[yelp-xsl] Updating jquery.syntax to 3.1 git tag for non-linking param



commit 062c0e4e7856e8171d830abf26655e1b496eb02b
Author: Shaun McCance <shaunm gnome org>
Date:   Fri Mar 2 15:59:08 2012 -0500

    Updating jquery.syntax to 3.1 git tag for non-linking param

 js/Makefile.am                           |    9 +-
 js/jquery.syntax.brush.bash.js           |    5 +
 js/jquery.syntax.brush.clang.js          |   13 +-
 js/jquery.syntax.brush.css.js            |   15 +-
 js/jquery.syntax.brush.go.js             |   47 +++
 js/jquery.syntax.brush.javascript.js     |    2 +-
 js/jquery.syntax.brush.ocaml.js          |   73 +++++
 js/jquery.syntax.brush.ooc.js            |    1 +
 js/jquery.syntax.brush.perl5.js          |    2 +-
 js/jquery.syntax.brush.php-script.js     |    6 +
 js/jquery.syntax.brush.protobuf.js       |   43 +++
 js/jquery.syntax.brush.ruby.js           |   15 +-
 js/jquery.syntax.brush.scala.js          |   44 +++
 js/jquery.syntax.brush.super-collider.js |   57 ++++
 js/jquery.syntax.brush.xml.js            |   52 ++--
 js/jquery.syntax.core.js                 |  487 +++++++++++++++++++++---------
 js/jquery.syntax.js                      |   19 +-
 js/jquery.syntax.layout.editor.js        |  291 ++++++++++++++++++
 js/jquery.syntax.layout.inline.js        |    6 +-
 xslt/common/html.xsl                     |    3 +-
 20 files changed, 996 insertions(+), 194 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index 3478d5f..6fbd4a0 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -5,13 +5,14 @@ js_DATA =					\
 	jquery.syntax.brush.apache.js		\
 	jquery.syntax.brush.applescript.js	\
 	jquery.syntax.brush.assembly.js		\
-	jquery.syntax.brush.bash.js		\
 	jquery.syntax.brush.bash-script.js	\
+	jquery.syntax.brush.bash.js		\
 	jquery.syntax.brush.basic.js		\
 	jquery.syntax.brush.clang.js		\
 	jquery.syntax.brush.csharp.js		\
 	jquery.syntax.brush.css.js		\
 	jquery.syntax.brush.diff.js		\
+	jquery.syntax.brush.go.js		\
 	jquery.syntax.brush.haskell.js		\
 	jquery.syntax.brush.html.js		\
 	jquery.syntax.brush.io.js		\
@@ -20,16 +21,20 @@ js_DATA =					\
 	jquery.syntax.brush.kai.js		\
 	jquery.syntax.brush.lisp.js		\
 	jquery.syntax.brush.lua.js		\
+	jquery.syntax.brush.ocaml.js		\
 	jquery.syntax.brush.ooc.js		\
 	jquery.syntax.brush.pascal.js		\
 	jquery.syntax.brush.perl5.js		\
-	jquery.syntax.brush.php.js		\
 	jquery.syntax.brush.php-script.js	\
+	jquery.syntax.brush.php.js		\
 	jquery.syntax.brush.plain.js		\
+	jquery.syntax.brush.protobuf.js		\
 	jquery.syntax.brush.python.js		\
 	jquery.syntax.brush.ruby.js		\
+	jquery.syntax.brush.scala.js		\
 	jquery.syntax.brush.smalltalk.js	\
 	jquery.syntax.brush.sql.js		\
+	jquery.syntax.brush.super-collider.js	\
 	jquery.syntax.brush.xml.js		\
 	jquery.syntax.brush.yaml.js		\
 	jquery.syntax.core.js			\
diff --git a/js/jquery.syntax.brush.bash.js b/js/jquery.syntax.brush.bash.js
index bf7cc5c..d08464e 100644
--- a/js/jquery.syntax.brush.bash.js
+++ b/js/jquery.syntax.brush.bash.js
@@ -25,4 +25,9 @@ Syntax.register('bash', function(brush) {
 	
 	// Numbers
 	brush.push(Syntax.lib.webLink);
+	
+	brush.push({
+		klass: 'stderr',
+		allow: ['string', 'comment', 'constant', 'href']
+	});
 });
diff --git a/js/jquery.syntax.brush.clang.js b/js/jquery.syntax.brush.clang.js
index db22bed..ed30d05 100644
--- a/js/jquery.syntax.brush.clang.js
+++ b/js/jquery.syntax.brush.clang.js
@@ -9,13 +9,15 @@ Syntax.register('clang', function(brush) {
 	
 	var access = ["@private", "@protected", "@public", "@required", "@optional", "private", "protected", "public", "friend", "using"];
 	
-	var types = ["mutable", "auto", "const", "double", "float", "int", "short", "char", "long", "signed", "unsigned", "bool", "void", "typename", "id", "register", "wchar_t"];
+	var typeModifiers = ["mutable", "auto", "const", "register", "typename", "abstract"];
+	var types = ["double", "float", "int", "short", "char", "long", "signed", "unsigned", "bool", "void", "id"];
 	
 	var operators = ["+", "*", "/", "-", "&", "|", "~", "!", "%", "<", "=", ">", "[", "]", "new", "delete", "in"];
 	
 	var values = ["this", "true", "false", "NULL", "YES", "NO", "nil"];
 	
 	brush.push(values, {klass: 'constant'});
+	brush.push(typeModifiers, {klass: 'keyword'})
 	brush.push(types, {klass: 'type'});
 	brush.push(keywords, {klass: 'keyword'});
 	brush.push(operators, {klass: 'operator'});
@@ -33,7 +35,7 @@ Syntax.register('clang', function(brush) {
 	brush.push(propertyAttributes, {
 		klass: 'keyword',
 		only: ['objective-c-property']
-	})
+	});
 	
 	// Objective-C strings
 	
@@ -42,8 +44,13 @@ Syntax.register('clang', function(brush) {
 		klass: 'string'
 	});
 	
-	// Objective-C classes
+	// Objective-C classes, C++ classes, C types, etc.
 	brush.push(Syntax.lib.camelCaseType);
+	brush.push(Syntax.lib.cStyleType);
+	brush.push({
+		pattern: /(?:class|struct|enum|namespace)\s+([^{;\s]+)/gmi,
+		matches: Syntax.extractMatches({klass: 'type'})
+	});
 	
 	brush.push({
 		pattern: /#.*$/gmi,
diff --git a/js/jquery.syntax.brush.css.js b/js/jquery.syntax.brush.css.js
index 76df7fa..3a0fc5b 100644
--- a/js/jquery.syntax.brush.css.js
+++ b/js/jquery.syntax.brush.css.js
@@ -33,9 +33,18 @@ Syntax.register('css', function(brush) {
 		pattern: new RegExp(colorMatcher.join("|"), "gi"),
 		klass: 'color',
 		process: function (element, match) {
-			var text = jQuery(element).text();
-			var colorBox = jQuery('<span class="color-box"><span class="sample" style="background-color: ' + text + '"></span>&nbsp;&nbsp;</span>');
-			return jQuery(element).append(colorBox);
+			var text = Syntax.innerText(element);
+			var colourBox = document.createElement('span');
+			colourBox.className = 'colour-box';
+			
+			var sampleColour = document.createElement('span');
+			sampleColour.className = 'sample';
+			sampleColour.style.backgroundColor = text;
+			sampleColour.appendChild(document.createTextNode('&nbsp;&nbsp;'))
+			colourBox.appendChild(sampleColour);
+			
+			element.appendChild(colourBox);
+			return element;
 		}
 	});
 		
diff --git a/js/jquery.syntax.brush.go.js b/js/jquery.syntax.brush.go.js
new file mode 100644
index 0000000..a376487
--- /dev/null
+++ b/js/jquery.syntax.brush.go.js
@@ -0,0 +1,47 @@
+// brush: "go" aliases: []
+
+//	This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License.
+//	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
+//	See <jquery.syntax.js> for licensing details.
+
+Syntax.register('go', function(brush) {
+	var keywords = ["break", "default", "func", "interface", "select", "case", "defer", "go", "map", "struct", "chan", "else", "goto", "package", "switch", "const", "fallthrough", "if", "range", "type", "continue", "for", "import", "return", "var"];
+	
+	var types = [
+		/u?int\d*/g,
+		/float\d+/g,
+		/complex\d+/g,
+		"byte",
+		"uintptr",
+		"string",
+	];
+	
+	var operators = ["+", "&", "+=", "&=", "&&", "==", "!=", "-", "|", "-=", "|=", "||", "<", "<=", "*", "^", "*=", "^=", "<-", ">", ">=", "/", "<<", "/=", "<<=", "++", "=", ":=", ",", ";", "%", ">>", "%=", ">>=", "--", "!", "...", ".", ":", "&^", "&^="];
+	
+	var values = ["true", "false", "iota", "nil"];
+	
+	var functions = ["append", "cap", "close", "complex", "copy", "imag", "len", "make", "new", "panic", "print", "println", "real", "recover"];
+	
+	brush.push(values, {klass: 'constant'});
+	brush.push(types, {klass: 'type'});
+	brush.push(keywords, {klass: 'keyword'});
+	brush.push(operators, {klass: 'operator'});
+	brush.push(functions, {klass: 'function'});
+	
+	brush.push(Syntax.lib.cStyleFunction);
+	
+	brush.push(Syntax.lib.camelCaseType);
+	
+	brush.push(Syntax.lib.cStyleComment);
+	brush.push(Syntax.lib.cppStyleComment);
+	brush.push(Syntax.lib.webLink);
+	
+	// Strings
+	brush.push(Syntax.lib.singleQuotedString);
+	brush.push(Syntax.lib.doubleQuotedString);
+	brush.push(Syntax.lib.stringEscape);
+	
+	// Numbers
+	brush.push(Syntax.lib.decimalNumber);
+	brush.push(Syntax.lib.hexNumber);
+});
diff --git a/js/jquery.syntax.brush.javascript.js b/js/jquery.syntax.brush.javascript.js
index 193c3ee..d0ad541 100644
--- a/js/jquery.syntax.brush.javascript.js
+++ b/js/jquery.syntax.brush.javascript.js
@@ -15,7 +15,7 @@ Syntax.register('javascript', function(brush) {
 	brush.push(operators, {klass: 'operator'});
 	
 	// Regular expressions
-	brush.push(Syntax.lib.perlStyleRegularExpressions);
+	brush.push(Syntax.lib.perlStyleRegularExpression);
 	
 	// Camel Case Types
 	brush.push(Syntax.lib.camelCaseType);
diff --git a/js/jquery.syntax.brush.ocaml.js b/js/jquery.syntax.brush.ocaml.js
new file mode 100644
index 0000000..c0b7e20
--- /dev/null
+++ b/js/jquery.syntax.brush.ocaml.js
@@ -0,0 +1,73 @@
+// brush: "ocaml" aliases: ["ml", "sml", "fsharp"]
+
+//	This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License.
+//	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
+//	See <jquery.syntax.js> for licensing details.
+
+// This brush is based loosely on the following documentation:
+// http://msdn.microsoft.com/en-us/library/dd233230.aspx
+
+Syntax.register('ocaml', function(brush) {
+	var keywords = ["abstract", "and", "as", "assert", "begin", "class", "default", "delegate", "do", "done", "downcast", "downto", "elif", "else", "end", "exception", "extern", "finally", "for", "fun", "function", "if", "in", "inherit", "inline", "interface", "internal", "lazy", "let", "match", "member", "module", "mutable", "namespace", "new", "null", "of", "open", "or", "override", "rec", "return", "static", "struct", "then", "to", "try", "type", "upcast", "use", "val", "when", "while", "with", "yield", "asr", "land", "lor", "lsl", "lsr", "lxor", "mod", "sig", "atomic", "break", "checked", "component", "const", "constraint", "constructor", "continue", "eager", "event", "external", "fixed", "functor", "global", "include", "method", "mixin", "object", "parallel", "process", "protected", "pure", "sealed", "trait", "virtual", "volatile", "val"];
+	
+	var types = ["bool", "byte", "sbyte", /\bu?int\d*\b/g, "nativeint", "unativeint", "char", "string", "decimal", "unit", "void", "float32", "single", "float64", "double", "list", "array", "exn", "format", "fun", "option", "ref"];
+	
+	var operators = ["!", "<>", "%", "&", "*", "+", "-", "->", "/", "::", ":=", ":>", ":?", ":?>", "<", "=", ">", "?", "@", "^", "_", "`", "|", "~", "'", "[<", ">]", "<|", "|>", "[|", "|]", "(|", "|)", "(*", "*)", "in"];
+	
+	var values = ["true", "false"];
+	
+	var access = ["private", "public"];
+	
+	brush.push(access, {klass: 'access'});
+	brush.push(values, {klass: 'constant'});
+	brush.push(types, {klass: 'type'});
+	brush.push(keywords, {klass: 'keyword'});
+	brush.push(operators, {klass: 'operator'});
+	
+	// http://caml.inria.fr/pub/docs/manual-ocaml/manual011.html#module-path
+	// open [module-path], new [type]
+	brush.push({
+		pattern: /(?:open|new)\s+((?:\.?[a-z][a-z0-9]*)+)/gi,
+		matches: Syntax.extractMatches({klass: 'type'})
+	});
+	
+	// Functions
+	brush.push({
+		pattern: /(?:\.)([a-z_][a-z0-9_]+)/gi,
+		matches: Syntax.extractMatches({klass: 'function'})
+	});
+	
+	// Avoid highlighting keyword arguments as camel-case types.
+	brush.push({
+		pattern: /(?:\(|,)\s*(\w+\s*=)/g,
+		matches: Syntax.extractMatches({
+			klass: 'keyword-argument'
+		})
+	});
+	
+	// We need to modify cStyleFunction because "(*" is a comment token.
+	brush.push({
+		pattern: /([a-z_][a-z0-9_]*)\s*\((?!\*)/gi,
+		matches: Syntax.extractMatches({klass: 'function'})
+	});
+	
+	// Types
+	brush.push(Syntax.lib.camelCaseType);
+	
+	// Web Links
+	brush.push(Syntax.lib.webLink);
+	
+	// Comments
+	brush.push({
+		pattern: /\(\*[\s\S]*?\*\)/g,
+		klass: 'comment'
+	});
+	
+	// Strings
+	brush.push(Syntax.lib.doubleQuotedString);
+	brush.push(Syntax.lib.stringEscape);
+	
+	// Numbers
+	brush.push(Syntax.lib.decimalNumber);
+	brush.push(Syntax.lib.hexNumber);
+});
diff --git a/js/jquery.syntax.brush.ooc.js b/js/jquery.syntax.brush.ooc.js
index 000781b..133040c 100644
--- a/js/jquery.syntax.brush.ooc.js
+++ b/js/jquery.syntax.brush.ooc.js
@@ -28,6 +28,7 @@ Syntax.register('ooc', function(brush) {
 	
 	// ClassNames (CamelCase)
 	brush.push(Syntax.lib.camelCaseType);
+	brush.push(Syntax.lib.cStyleType);
 	brush.push(Syntax.lib.cStyleFunction);
 	
 	brush.push(Syntax.lib.cStyleComment);
diff --git a/js/jquery.syntax.brush.perl5.js b/js/jquery.syntax.brush.perl5.js
index 6cce322..4ec9c4d 100644
--- a/js/jquery.syntax.brush.perl5.js
+++ b/js/jquery.syntax.brush.perl5.js
@@ -19,7 +19,7 @@ Syntax.register('perl5', function(brush) {
 	brush.push(builtins, {klass: 'function'});
 	
 	// Regular expressions
-	brush.push(Syntax.lib.perlStyleRegularExpressions);
+	brush.push(Syntax.lib.perlStyleRegularExpression);
 	
 	// Comments
 	brush.push(Syntax.lib.perlStyleComment);
diff --git a/js/jquery.syntax.brush.php-script.js b/js/jquery.syntax.brush.php-script.js
index fab2ff7..c053fa0 100644
--- a/js/jquery.syntax.brush.php-script.js
+++ b/js/jquery.syntax.brush.php-script.js
@@ -18,6 +18,12 @@ Syntax.register('php-script', function(brush) {
 	brush.push(operators, {klass: 'operator'});
 	brush.push(access, {klass: 'access'});
 	
+	// Variables
+	brush.push({
+		pattern: /\$[a-z_][a-z0-9]*/gi,
+		klass: 'variable'
+	});
+	
 	// ClassNames (CamelCase)
 	brush.push(Syntax.lib.camelCaseType);
 	brush.push(Syntax.lib.cStyleFunction);
diff --git a/js/jquery.syntax.brush.protobuf.js b/js/jquery.syntax.brush.protobuf.js
new file mode 100644
index 0000000..1ebd8f3
--- /dev/null
+++ b/js/jquery.syntax.brush.protobuf.js
@@ -0,0 +1,43 @@
+// brush: "protobuf" aliases: []
+
+//	This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License.
+//	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
+//	See <jquery.syntax.js> for licensing details.
+
+Syntax.register('protobuf', function(brush) {
+	var keywords = ["enum", "extend", "extensions", "group", "import", "max", "message", "option", "package", "returns", "rpc", "service", "syntax", "to", "default"];
+	brush.push(keywords, {klass: 'keyword'})
+	
+	var values = ["true", "false"];
+	brush.push(values, {klass: 'constant'});
+	
+	var types = ["bool", "bytes", "double", "fixed32", "fixed64", "float", "int32", "int64", "sfixed32", "sfixed64", "sint32", "sint64", "string", "uint32", "uint64"];
+	brush.push(types, {klass: 'type'});
+	
+	var access = ["optional", "required", "repeated"]
+	brush.push(access, {klass: 'access'});
+		
+	brush.push(Syntax.lib.camelCaseType);
+	
+	// Highlight names of fields
+	brush.push({
+		pattern: /\s+(\w+)\s*=\s*\d+/g,
+		matches: Syntax.extractMatches({
+			klass: 'variable'
+		})
+	});
+	
+	// Comments
+	brush.push(Syntax.lib.cStyleComment);
+	brush.push(Syntax.lib.webLink);
+	
+	// Strings
+	brush.push(Syntax.lib.singleQuotedString);
+	brush.push(Syntax.lib.doubleQuotedString);
+	brush.push(Syntax.lib.stringEscape);
+	
+	// Numbers
+	brush.push(Syntax.lib.decimalNumber);
+	brush.push(Syntax.lib.hexNumber);
+});
+
diff --git a/js/jquery.syntax.brush.ruby.js b/js/jquery.syntax.brush.ruby.js
index 9332890..5d37ed1 100644
--- a/js/jquery.syntax.brush.ruby.js
+++ b/js/jquery.syntax.brush.ruby.js
@@ -30,8 +30,21 @@ Syntax.register('ruby', function(brush) {
 		matches: Syntax.extractMatches({klass: 'function'}, {klass: 'constant'})
 	});
 	
+	brush.push({
+		pattern: /`[^`]+`/g,
+		klass: 'string'
+	});
+	
+	brush.push({
+		pattern: /\#\{([^\}]*)\}/g,
+		matches: Syntax.extractMatches({
+			brush: 'ruby',
+			only: ['string']
+		}),
+	});
+	
 	// Regular expressions
-	brush.push(Syntax.lib.perlStyleRegularExpressions);
+	brush.push(Syntax.lib.perlStyleRegularExpression);
 	
 	brush.push({pattern: /(@+|\$)[\w]+/g, klass: 'variable'});
 	
diff --git a/js/jquery.syntax.brush.scala.js b/js/jquery.syntax.brush.scala.js
new file mode 100644
index 0000000..544a26b
--- /dev/null
+++ b/js/jquery.syntax.brush.scala.js
@@ -0,0 +1,44 @@
+// brush: "scala" aliases: []
+
+//	This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License.
+//	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
+//	See <jquery.syntax.js> for licensing details.
+
+Syntax.brushes.dependency('scala', 'xml');
+
+Syntax.register('scala', function(brush) {
+	var keywords = ["abstract", "do", "finally", "import", "object", "return", "trait", "var", "case", "catch", "class", "else", "extends", "for", "forSome", "if", "lazy", "match", "new", "override", "package", "private", "sealed", "super", "try", "type", "while", "with", "yield", "def", "final", "implicit", "protected", "throw", "val"];
+	brush.push(keywords, {klass: 'keyword'});
+	
+	var operators = ["_", ":", "=", "=>", "<-", "<:", "<%", ">:", "#", "@"];
+	brush.push(operators, {klass: 'operator'});
+	
+	var constants = ["this", "null", "true", "false"];
+	brush.push(constants, {klass: 'constant'});
+	
+	// Strings
+	brush.push({
+		pattern: /"""[\s\S]*?"""/g,
+		klass: 'string'
+	});
+	
+	brush.push(Syntax.lib.doubleQuotedString);
+	
+	// Functions
+	brush.push({
+		pattern: /(?:def\s+|\.)([a-z_][a-z0-9_]+)/gi, 
+		matches: Syntax.extractMatches({klass: 'function'})
+	});
+	
+	brush.push(Syntax.lib.camelCaseType);
+	
+	// Types
+	brush.push(Syntax.lib.cStyleFunction);
+	
+	// Comments
+	brush.push(Syntax.lib.cStyleComment);
+	brush.push(Syntax.lib.cppStyleComment);
+	
+	brush.derives('xml');
+});
+
diff --git a/js/jquery.syntax.brush.super-collider.js b/js/jquery.syntax.brush.super-collider.js
new file mode 100644
index 0000000..4064ce1
--- /dev/null
+++ b/js/jquery.syntax.brush.super-collider.js
@@ -0,0 +1,57 @@
+// brush: "super-collider" aliases: ["sc"]
+
+//	This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License.
+//	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
+//	See <jquery.syntax.js> for licensing details.
+
+Syntax.register('super-collider', function(brush) {
+	var keywords = ["const", "arg", "classvar", "var"];
+	brush.push(keywords, {klass: 'keyword'});
+	
+	var operators = ["`", "+", "@", ":", "*", "/", "-", "&", "|", "~", "!", "%", "<", "=", ">"];
+	brush.push(operators, {klass: 'operator'});
+	
+	var values = ["thisFunctionDef", "thisFunction", "thisMethod", "thisProcess", "thisThread", "this", "super", "true", "false", "nil", "inf"];
+	brush.push(values, {klass: 'constant'});
+	
+	brush.push(Syntax.lib.camelCaseType);
+	
+	// Single Characters
+	brush.push({
+		pattern: /\$(\\)?./g,
+		klass: "constant"
+	});
+	
+	// Symbols
+	brush.push({
+		pattern: /\\[a-z_][a-z0-9_]*/gi,
+		klass: "symbol"
+	});
+	
+	brush.push({
+		pattern: /'[^']+'/g,
+		klass: "symbol"
+	});
+	
+	// Comments
+	brush.push(Syntax.lib.cStyleComment);
+	brush.push(Syntax.lib.cppStyleComment);
+	brush.push(Syntax.lib.webLink);
+	
+	// Strings
+	brush.push(Syntax.lib.singleQuotedString);
+	brush.push(Syntax.lib.doubleQuotedString);
+	brush.push(Syntax.lib.stringEscape);
+	
+	// Numbers
+	brush.push(Syntax.lib.decimalNumber);
+	brush.push(Syntax.lib.hexNumber);
+	
+	// Functions
+	brush.push({
+		pattern: /(?:\.)([a-z_][a-z0-9_]*)/gi, 
+		matches: Syntax.extractMatches({klass: 'function'})
+	});
+	
+	brush.push(Syntax.lib.cStyleFunction);
+});
diff --git a/js/jquery.syntax.brush.xml.js b/js/jquery.syntax.brush.xml.js
index 69a4b47..03bbbf2 100644
--- a/js/jquery.syntax.brush.xml.js
+++ b/js/jquery.syntax.brush.xml.js
@@ -4,26 +4,10 @@
 //	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
 //	See <jquery.syntax.js> for licensing details.
 
-Syntax.register('xml', function(brush) {
-	brush.push({
-		pattern: /(<!(\[CDATA\[)([\s\S]*?)(\]\])>)/gm,
-		matches: Syntax.extractMatches(
-			{klass: 'cdata', allow: ['cdata-content', 'cdata-tag']},
-			{klass: 'cdata-tag'},
-			{klass: 'cdata-content'},
-			{klass: 'cdata-tag'}
-		)
-	});
-	
-	brush.push(Syntax.lib.xmlComment);
-	
-	// /[\s\S]/ means match anything... /./ doesn't match newlines
-	brush.push({
-		pattern: /<[^>]+>/g,
-		klass: 'tag',
-		allow: '*'
-	});
-	
+Syntax.lib.xmlEntity = {pattern: /&\w+;/g, klass: 'entity'};
+Syntax.lib.xmlPercentEscape = {pattern: /(%[0-9a-f]{2})/gi, klass: 'percent-escape', only: ['string']};
+
+Syntax.register('xml-tag', function(brush) {
 	brush.push({
 		pattern: /<\/?((?:[^:\s>]+:)?)([^\s>]+)(\s[^>]*)?\/?>/g,
 		matches: Syntax.extractMatches({klass: 'namespace'}, {klass: 'tag-name'})
@@ -34,19 +18,33 @@ Syntax.register('xml', function(brush) {
 		matches: Syntax.extractMatches({klass: 'attribute', only: ['tag']}, {klass: 'string', only: ['tag']})
 	});
 	
+	brush.push(Syntax.lib.xmlEntity);
+	brush.push(Syntax.lib.xmlPercentEscape);
+	
+	brush.push(Syntax.lib.singleQuotedString);
+	brush.push(Syntax.lib.doubleQuotedString);
+});
+
+Syntax.register('xml', function(brush) {
 	brush.push({
-		pattern: /&\w+;/g,
-		klass: 'entity'
+		pattern: /(<!(\[CDATA\[)([\s\S]*?)(\]\])>)/gm,
+		matches: Syntax.extractMatches(
+			{klass: 'cdata', allow: ['cdata-content', 'cdata-tag']},
+			{klass: 'cdata-tag'},
+			{klass: 'cdata-content'},
+			{klass: 'cdata-tag'}
+		)
 	});
 	
+	brush.push(Syntax.lib.xmlComment);
+	
 	brush.push({
-		pattern: /(%[0-9a-f]{2})/gi,
-		klass: 'percent-escape',
-		only: ['string']
+		pattern: /<[^>\-\s]([^>'"!\/;\? \[\]^`\{\}\|]|"[^"]*"|'[^']')*[\/?]?>/g,
+		brush: 'xml-tag'
 	});
 	
-	brush.push(Syntax.lib.singleQuotedString);
-	brush.push(Syntax.lib.doubleQuotedString);
+	brush.push(Syntax.lib.xmlEntity);
+	brush.push(Syntax.lib.xmlPercentEscape);
 	
 	brush.push(Syntax.lib.webLink);
 });
diff --git a/js/jquery.syntax.core.js b/js/jquery.syntax.core.js
index 4cbd9c9..ecc71f8 100644
--- a/js/jquery.syntax.core.js
+++ b/js/jquery.syntax.core.js
@@ -20,37 +20,26 @@ if (!String.prototype.repeat) {
 	};
 }
 
-// The jQuery version of container.text() is broken on IE6.
-// This version fixes it... for pre elements only. Other elements
-// in IE will have the whitespace manipulated.
-Syntax.getCDATA = function (elems) {
-	var cdata = "", elem;
+// Return the inner text of an element - must preserve whitespace.
+// Avoid returning \r characters.
+Syntax.innerText = function(element) {
+	var text;
 	
-	(function (elems) {
-		for (var i = 0; elems[i]; i++) {
-			elem = elems[i];
-
-			// Get the text from text nodes and CDATA nodes
-			if (elem.nodeType === 3 || elem.nodeType === 4) {
-				cdata += elem.nodeValue;
-		
-			// Use textContent || innerText for elements
-			} else if (elem.nodeType === 1) {
-				if (typeof(elem.textContent) === 'string')
-					cdata += elem.textContent;
-				else if (typeof(elem.innerText) === 'string')
-					cdata += elem.innerText;
-				else
-					arguments.callee(elem.childNodes);
-			
-			// Traverse everything else, except comment nodes
-			} else if (elem.nodeType !== 8) {
-				arguments.callee(elem.childNodes);
-			}
-		}
-	})(elems);
+	if (!element) {
+		return "";
+	}
 	
-	return cdata.replace(/\r\n?/g, "\n");
+	if (element.nodeName == 'BR') {
+		return '\n';
+	} else if (element.textContent) {
+		// W3C: FF, Safari, Chrome, etc.
+		text = element.textContent;
+	} else if (document.body.innerText) {
+		// IE, other older browsers.
+		text = element.innerText;
+	}
+	
+	return text.replace(/\r\n?/g, '\n');
 }
 
 // Convert to stack based implementation
@@ -67,14 +56,18 @@ Syntax.extractElementMatches = function (elems, offset, tabWidth) {
 				offset += elem.nodeValue.length;
 			
 			} else if (elem.nodeType === 1) {
-				var text = Syntax.getCDATA(elem.childNodes);
-				var expr = {klass: elem.className, force: true, element: elem};
+				var text = Syntax.innerText(elem);
 				
-				matches.push(new Syntax.Match(offset, text.length, expr, text));
+				matches.push(new Syntax.Match(offset, text.length, {
+					klass: elem.className,
+					force: true,
+					element: elem,
+					allow: '*'
+				}, text));
 			}
 			
 			// Traverse everything, except comment nodes
-			if (elem.nodeType !== 8) {
+			if (elem.nodeType !== 8 && elem.children) {
 				arguments.callee(elem.childNodes, offset);
 			}
 		}
@@ -87,6 +80,7 @@ Syntax.extractElementMatches = function (elems, offset, tabWidth) {
 	return matches;
 }
 
+// Basic layout doesn't do anything e.g. identity layout.
 Syntax.layouts.preformatted = function (options, html, container) {
 	return html;
 };
@@ -95,6 +89,7 @@ Syntax.modeLineOptions = {
 	'tab-width': function(name, value, options) { options.tabWidth = parseInt(value, 10); }
 };
 
+// Should be obvious right?
 Syntax.convertTabsToSpaces = function (text, tabSize) {
 	var space = [], pattern = /\r|\n|\t/g, tabOffset = 0, offsets = [], totalOffset = 0;
 	tabSize = tabSize || 4
@@ -123,6 +118,15 @@ Syntax.convertTabsToSpaces = function (text, tabSize) {
 	return {text: text, offsets: offsets};
 };
 
+// This function converts from a compressed set of offsets of the form:
+//	[
+//		[offset, width, totalOffset],
+//		...
+//	]
+// This means that at a $offset, a tab (single character) was expanded to $width
+// single space characters.
+// This function produces a lookup table of offsets, where a given character offset
+// is mapped to how far the character has been offset.
 Syntax.convertToLinearOffsets = function (offsets, length) {
 	var current = 0, changes = [];
 	
@@ -130,11 +134,19 @@ Syntax.convertToLinearOffsets = function (offsets, length) {
 	// has been shifted right by offset[current][2]
 	for (var i = 0; i < length; i++) {
 		if (offsets[current] && i > offsets[current][0]) {
-			if (offsets[current+1] && i <= offsets[current+1][0]) {
-				changes.push(offsets[current][2]);
+			// Is there a next offset?
+			if (offsets[current+1]) {
+				// Is the index less than the start of the next offset?
+				if (i <= offsets[current+1][0]) {
+					changes.push(offsets[current][2]);
+				} else {
+					// If so, move to the next offset.
+					current += 1;
+					i -= 1;
+				}
 			} else {
-				current += 1;
-				i -= 1;
+				// If there is no next offset we assume this one to the end.
+				changes.push(offsets[current][2]);
 			}
 		} else {
 			changes.push(changes[changes.length-1] || 0);
@@ -144,6 +156,8 @@ Syntax.convertToLinearOffsets = function (offsets, length) {
 	return changes;
 }
 
+// Used for tab expansion process, by shifting matches when tab charaters were converted to
+// spaces.
 Syntax.updateMatchesWithOffsets = function (matches, linearOffsets, text) {
 	(function (matches) {
 		for (var i = 0; i < matches.length; i++) {
@@ -165,6 +179,9 @@ Syntax.updateMatchesWithOffsets = function (matches, linearOffsets, text) {
 	return matches;
 };
 
+// A helper function which automatically matches expressions with capture groups from the regular expression match.
+// Each argument position corresponds to the same index regular expression group.
+// Or, override by providing rule.index
 Syntax.extractMatches = function() {
 	var rules = arguments;
 	
@@ -188,7 +205,7 @@ Syntax.extractMatches = function() {
 			
 			if (match[index].length > 0) {
 				if (rule.brush) {
-					matches.push(Syntax.brushes[rule.brush].buildTree(match[index], RegExp.indexOf(match, index)));
+					matches.push(Syntax.Brush.buildTree(rule, match[index], RegExp.indexOf(match, index)));
 				} else {
 					var expression = jQuery.extend({owner: expr.owner}, rule);
 					
@@ -201,19 +218,30 @@ Syntax.extractMatches = function() {
 	};
 };
 
+// Used to create processing functions that automatically link to remote documentation.
 Syntax.lib.webLinkProcess = function (queryURI, lucky) {
 	if (lucky) {
 		queryURI = "http://www.google.com/search?btnI=I&q="; + encodeURIComponent(queryURI + " ");
 	}
 	
-	return function (element, match) {
-		return jQuery('<a>').
-			attr('href', queryURI + encodeURIComponent(element.text())).
-			attr('class', element.attr('class')).
-			append(element.contents());
+	return function (element, match, options) {
+		// Per-code block linkification control.
+		if (options.linkify === false)
+			return element;
+		
+		var a = document.createElement('a');
+		a.href = queryURI + encodeURIComponent(Syntax.innerText(element));
+		a.className = element.className;
+		
+		// Move children from <element> to <a>
+		while (element.childNodes.length > 0)
+			a.appendChild(element.childNodes[0]);
+		
+		return a;
 	};
 };
 
+// Global brush registration function.
 Syntax.register = function (name, callback) {
 	var brush = Syntax.brushes[name] = new Syntax.Brush();
 	brush.klass = name;
@@ -221,14 +249,16 @@ Syntax.register = function (name, callback) {
 	callback(brush);
 };
 
+// Library of helper patterns
 Syntax.lib.cStyleComment = {pattern: /\/\*[\s\S]*?\*\//gm, klass: 'comment', allow: ['href']};
 Syntax.lib.cppStyleComment = {pattern: /\/\/.*$/gm, klass: 'comment', allow: ['href']};
 Syntax.lib.perlStyleComment = {pattern: /#.*$/gm, klass: 'comment', allow: ['href']};
 
-Syntax.lib.perlStyleRegularExpressions = {pattern: /\B\/([^\/]|\\\/)*?\/[a-z]*(?=\s*[^\w\s'";\/])/g, klass: 'constant'};
+Syntax.lib.perlStyleRegularExpression = {pattern: /\B\/([^\/]|\\\/)*?\/[a-z]*(?=\s*($|[^\w\s'"\(]))/gm, klass: 'constant', incremental: true};
 
 Syntax.lib.cStyleFunction = {pattern: /([a-z_][a-z0-9_]*)\s*\(/gi, matches: Syntax.extractMatches({klass: 'function'})};
 Syntax.lib.camelCaseType = {pattern: /\b_*[A-Z][\w]*\b/g, klass: 'type'};
+Syntax.lib.cStyleType = {pattern: /\b[_a-z][_\w]*_t\b/gi, klass: 'type'};
 
 Syntax.lib.xmlComment = {pattern: /(&lt;|<)!--[\s\S]*?--(&gt;|>)/gm, klass: 'comment'};
 Syntax.lib.webLink = {pattern: /\w+:\/\/[\w\-.\/?%&=@:;#]*/g, klass: 'href'};
@@ -242,6 +272,7 @@ Syntax.lib.multiLineDoubleQuotedString = {pattern: /"([^\\"]|\\.)*"/g, klass: 's
 Syntax.lib.multiLineSingleQuotedString = {pattern: /'([^\\']|\\.)*'/g, klass: 'string'};
 Syntax.lib.stringEscape = {pattern: /\\./g, klass: 'escape', only: ['string']};
 
+// Main match constructor. Make sure value is the correct size.
 Syntax.Match = function (offset, length, expression, value) {
 	this.offset = offset;
 	this.endOffset = offset + length;
@@ -279,34 +310,39 @@ Syntax.Match.prototype.adjust = function (offset, length, text) {
 	}
 };
 
+// Sort helper for sorting matches in forward order (e.g. same as the text that they were extracted from)
 Syntax.Match.sort = function (a,b) {
 	return (a.offset - b.offset) || (b.length - a.length);
 };
 
+// Is the given match contained in the range of the parent match?
 Syntax.Match.prototype.contains = function (match) {
 	return (match.offset >= this.offset) && (match.endOffset <= this.endOffset);
 };
 
+// Reduce a givent tree node into an html node.
 Syntax.Match.defaultReduceCallback = function (node, container) {
 	// We avoid using jQuery in this function since it is incredibly performance sensitive.
 	// Using jQuery jQuery.fn.append() can reduce performance by as much as 1/3rd.
 	if (typeof(node) === 'string') {
 		node = document.createTextNode(node);
-	} else {
-		node = node[0];
 	}
 	
-	container[0].appendChild(node);
+	container.appendChild(node);
 };
 
+// Convert a tree of matches into some flat form (typically HTML nodes).
 Syntax.Match.prototype.reduce = function (append, process) {
 	var start = this.offset;
-	var container = jQuery('<span></span>');
+	var container = document.createElement('span');
 	
 	append = append || Syntax.Match.defaultReduceCallback;
 	
 	if (this.expression && this.expression.klass) {
-		container.addClass(this.expression.klass);
+		if (container.className.length > 0)
+			container.className += ' ';
+		
+		container.className += this.expression.klass;
 	}
 	
 	for (var i = 0; i < this.children.length; i += 1) {
@@ -339,6 +375,7 @@ Syntax.Match.prototype.reduce = function (append, process) {
 	return container;
 };
 
+// Main nesting check - can a match contain the given match?
 Syntax.Match.prototype.canContain = function (match) {
 	// This is a special conditional for explicitly added ranges by the user.
 	// Since user added it, we honour it no matter what.
@@ -379,6 +416,8 @@ Syntax.Match.prototype.canContain = function (match) {
 	return false;
 };
 
+// Return true if the given match can be spliced in as a child.
+// Checked automatically when calling _splice.
 Syntax.Match.prototype.canHaveChild = function(match) {
 	var only = match.expression.only;
 	
@@ -405,6 +444,9 @@ Syntax.Match.prototype.canHaveChild = function(match) {
 	return true;
 };
 
+// Add a child into the list of children for a given match, if it is acceptable to do so.
+// Updates the owner of the match.
+// Returns null if splice failed.
 Syntax.Match.prototype._splice = function(i, match) {
 	if (this.canHaveChild(match)) {
 		this.children.splice(i, 0, match);
@@ -424,19 +466,70 @@ Syntax.Match.prototype._splice = function(i, match) {
 // This function implements a full insertion procedure, and will break up the match to fit.
 // This operation is potentially very expensive, but is used to insert custom ranges into
 // the tree, if they are specified by the user. A custom <span> may cover multiple leafs in
-// the tree, thus naturally it needs to be broken up.
+// the tree, thus some parts of the tree may need to be split. This behavior is controlled
+// by whole - if true, the tree is split, if false, the match is split.
 // You should avoid using this function except in very specific cases.
-Syntax.Match.prototype.insert = function(match) {
+Syntax.Match.prototype.insert = function(match, whole) {
 	if (!this.contains(match))
 		return null;
 	
-	return this._insert(match);
+	if (whole) {
+		var top = this, i = 0;
+		while (i < top.children.length) {
+			if (top.children[i].contains(match)) {
+				top = top.children[i];
+				i = 0;
+			} else {
+				i += 1;
+			}
+		}
+		
+		return top._insertWhole(match);
+	} else {
+		return this._insert(match);
+	}
+}
+
+Syntax.Match.prototype._insertWhole = function(match) {
+	var parts = this.bisectAtOffsets([match.offset, match.endOffset])
+	this.children = [];
+	
+	if (parts[0]) {
+		this.children = this.children.concat(parts[0].children);
+	}
+	
+	if (parts[1]) {
+		match.children = [];
+		
+		// Update the match's expression based on the current position in the tree:
+		if (this.expression && this.expression.owner) {
+			match.expression = this.expression.owner.getRuleForKlass(match.expression.klass) || match.expression;
+		}
+		
+		// This probably isn't ideal, it would be better to convert all children and children-of-children
+		// into a linear array and reinsert - it would be slightly more accurate in many cases.
+		for (var i = 0; i < parts[1].children.length; i += 1) {
+			var child = parts[1].children[i];
+			
+			if (match.canContain(child)) {
+				match.children.push(child);
+			}
+		}
+		
+		this.children.push(match);
+	}
+	
+	if (parts[2]) {
+		this.children = this.children.concat(parts[2].children);
+	}
+	
+	return this;
 }
 
 // This is not a general tree insertion function. It is optimised to run in almost constant
 // time, but data must be inserted in sorted order, otherwise you will have problems.
 // This function also ensures that matches won't be broken up unless absolutely necessary.
-Syntax.Match.prototype.insertAtEnd = function (match) {
+Syntax.Match.prototype.insertAtEnd = function(match) {
 	if (!this.contains(match)) {
 		Syntax.log("Syntax Error: Child is not contained in parent node!");
 		return null;
@@ -493,7 +586,8 @@ Syntax.Match.prototype.insertAtEnd = function (match) {
 };
 
 // This insertion function is relatively complex because it is required to split the match over
-// several children.
+// several children. This function is used infrequently and is mostly for completeness. However,
+// it might be possible to remove it to reduce code.
 Syntax.Match.prototype._insert = function(match) {
 	if (this.children.length == 0)
 		return this._splice(0, match);
@@ -522,9 +616,9 @@ Syntax.Match.prototype._insert = function(match) {
 			return child._insert(match);
 		}
 		
-		console.log("Bisect at offsets", match, child.offset, child.endOffset);
+		// console.log("Bisect at offsets", match, child.offset, child.endOffset);
 		var parts = match.bisectAtOffsets([child.offset, child.endOffset]);
-		console.log("parts =", parts);
+		// console.log("parts =", parts);
 		// We now have at most three parts
 		//           {------child------}   {---possibly some other child---}
 		//   |--[0]--|-------[1]-------|--[2]--|
@@ -695,6 +789,9 @@ Syntax.Match.prototype.bisectAtOffsets = function(splits) {
 	return parts;
 };
 
+// Split a match at points in the tree that match a specific regular expression pattern.
+// Uses the fast tree bisection algorithm, performance should be bounded O(S log N) where N is
+// the total number of matches and S is the number of splits (?).
 Syntax.Match.prototype.split = function(pattern) {
 	var splits = [], match;
 	
@@ -724,6 +821,28 @@ Syntax.Brush = function () {
 	this.processes = {};
 };
 
+// Add a parent to the brush. This brush should be loaded as a dependency.
+Syntax.Brush.prototype.derives = function (name) {
+	this.parents.push(name);
+	this.rules.push({
+		apply: function(text, expr) {
+			return Syntax.brushes[name].getMatches(text);
+		}
+	});
+}
+
+// Return an array of all classes that the brush consists of.
+// A derivied brush is its own klass + the klass of any and all parents.
+Syntax.Brush.prototype.allKlasses = function () {
+	var klasses = [this.klass];
+	
+	for (var i = 0; i < this.parents.length; i += 1) {
+		klasses = klasses.concat(Syntax.brushes[this.parents[i]].allKlasses());
+	}
+	
+	return klasses;
+}
+
 Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {
 	var prefix = "\\b", postfix = "\\b";
 	
@@ -745,35 +864,55 @@ Syntax.Brush.convertStringToTokenPattern = function (pattern, escape) {
 	return prefix + pattern + postfix;
 }
 
-// Add a parent to the brush. This brush should be loaded as a dependency.
-Syntax.Brush.prototype.derives = function (name) {
-	this.parents.push(name);
-	this.rules.push({
-		apply: function(text, expr, offset) {
-			return Syntax.brushes[name].getMatches(text, offset);
-		}
-	});
-}
-
-// Return an array of all classes that the brush consists of.
-// A derivied brush is its own klass + the klass of any and all parents.
-Syntax.Brush.prototype.allKlasses = function () {
-	var klasses = [this.klass];
+Syntax.Brush.MatchPattern = function (text, rule) {
+	if (!rule.pattern)
+		return [];
 	
-	for (var i = 0; i < this.parents.length; i += 1) {
-		klasses = klasses.concat(Syntax.brushes[this.parents[i]].allKlasses());
+	// Duplicate the pattern so that the function is reentrant.
+	var matches = [], pattern = new RegExp;
+	pattern.compile(rule.pattern);
+	
+	while((match = pattern.exec(text)) !== null) {
+		if (rule.matches) {
+			matches = matches.concat(rule.matches(match, rule));
+		} else if (rule.brush) {
+			matches.push(Syntax.Brush.buildTree(rule, match[0], match.index));
+		} else {
+			matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
+		}
+		
+		if (rule.incremental) {
+			// Don't start scanning from the end of the match..
+			pattern.lastIndex = match.index + 1;
+		}
 	}
 	
-	return klasses;
+	return matches;
 }
 
 Syntax.Brush.prototype.push = function () {
 	if (jQuery.isArray(arguments[0])) {
 		var patterns = arguments[0], rule = arguments[1];
 		
+		var all = "(";
+		
 		for (var i = 0; i < patterns.length; i += 1) {
-			this.push(jQuery.extend({pattern: patterns[i]}, rule));
+			if (i > 0) all += "|";
+			
+			var p = patterns[i];
+			
+			if (p instanceof RegExp) {
+				all += p.source;
+			} else {
+				all += Syntax.Brush.convertStringToTokenPattern(p, true);
+			}
 		}
+		
+		all += ")";
+		
+		this.push(jQuery.extend({
+			pattern: new RegExp(all, rule.options || 'g')
+		}, rule));
 	} else {
 		var rule = arguments[0];
 		
@@ -785,67 +924,84 @@ Syntax.Brush.prototype.push = function () {
 		if (typeof(XRegExp) !== 'undefined') {
 			rule.pattern = new XRegExp(rule.pattern);
 		}
+		
+		// Default pattern extraction algorithm
+		rule.apply = rule.apply || Syntax.Brush.MatchPattern;
 
-		if (rule.pattern && rule.pattern.global) {
+		if (rule.pattern && rule.pattern.global || typeof(rule.pattern) == 'undefined') {
 			this.rules.push(jQuery.extend({owner: this}, rule));
-		} else if (typeof(console) != "undefined") {
+		} else {
 			Syntax.log("Syntax Error: Malformed rule: ", rule);
 		}
 	}
 };
 
-Syntax.Brush.prototype.getMatchesForRule = function (text, rule, offset) {
+Syntax.Brush.prototype.getMatchesForRule = function (text, rule) {
 	var matches = [], match = null;
 	
 	// Short circuit (user defined) function:
-	if (typeof rule.apply != "undefined") {
-		return rule.apply(text, rule, offset);
-	}
-	
-	// Duplicate the pattern so that the function is reentrant.
-	var pattern = new RegExp;
-	pattern.compile(rule.pattern);
-	
-	while((match = pattern.exec(text)) !== null) {
-		if (rule.matches) {
-			matches = matches.concat(rule.matches(match, rule));
-		} else if (rule.brush) {
-			matches.push(Syntax.brushes[rule.brush].buildTree(match[0], match.index));
-		} else {
-			matches.push(new Syntax.Match(match.index, match[0].length, rule, match[0]));
-		}
-	}
-	
-	if (offset && offset > 0) {
-		for (var i = 0; i < matches.length; i += 1) {
-			matches[i].shift(offset);
-		}
+	if (typeof(rule.apply) != 'undefined') {
+		matches = rule.apply(text, rule);
 	}
 	
 	if (rule.debug) {
-		Syntax.log("matches", matches);
+		Syntax.log("Syntax matches:", rule, text, matches);
 	}
 	
 	return matches;
 };
 
-Syntax.Brush.prototype.getMatches = function(text, offset) {
+Syntax.Brush.prototype.getRuleForKlass = function (klass) {
+	for (var i = 0; i < this.rules.length; i += 1) {
+		if (this.rules[i].klass == klass) {
+			return this.rules[i];
+		}
+	}
+	
+	return null;
+}
+
+// Get all matches from a given block of text.
+Syntax.Brush.prototype.getMatches = function(text) {
 	var matches = [];
 	
 	for (var i = 0; i < this.rules.length; i += 1) {
-		matches = matches.concat(this.getMatchesForRule(text, this.rules[i], offset));
+		matches = matches.concat(this.getMatchesForRule(text, this.rules[i]));
 	}
 	
 	return matches;
 };
 
+// A helper function for building a tree from a specific rule.
+// Typically used where sub-trees are required, e.g. CSS brush in HTML brush.
+Syntax.Brush.buildTree = function(rule, text, offset, additionalMatches) {
+	var match = Syntax.brushes[rule.brush].buildTree(text, offset, additionalMatches);
+	
+	jQuery.extend(match.expression, rule);
+	
+	return match;
+}
+
+// This function builds a tree from a given block of text.
+// This is done by applying all rules to the text to get a complete list of matches,
+// sorting them in order, and inserting them into a syntax tree data structure.
+// Additional matches are forcefully inserted into the tree.
+// Provide an offset if the text is offset in a larger block of text. Matches
+// will be shifted along appropriately.
 Syntax.Brush.prototype.buildTree = function(text, offset, additionalMatches) {
 	offset = offset || 0;
 	
 	// Fixes code that uses \r\n for line endings. /$/ matches both \r\n, which is a problem..
-	text = text.replace(/\r/g, "");
+	text = text.replace(/\r/g, '');
 	
-	var matches = this.getMatches(text, offset);
+	var matches = this.getMatches(text);
+	
+	// Shift matches if offset is provided.
+	if (offset && offset > 0) {
+		for (var i = 0; i < matches.length; i += 1) {
+			matches[i].shift(offset);
+		}
+	}
 	
 	var top = new Syntax.Match(offset, text.length, {klass: this.allKlasses().join(" "), allow: '*', owner: this}, text);
 
@@ -867,35 +1023,85 @@ Syntax.Brush.prototype.buildTree = function(text, offset, additionalMatches) {
 	return top;
 };
 
-// Matches is optional, and provides a set of pre-existing matches.
-Syntax.Brush.prototype.process = function(text, matches) {
+// This function builds a syntax tree from the given text and matches (optional).
+// The syntax tree is then flattened into html using a variety of functions.
+//
+// By default, you can't control reduction process through this function, but
+// it is possible to control the element conversion process by replace
+// .reduce(null, ...) with  .reduce(reduceCallback, ...)
+// See Syntax.Match.defaultReduceCallback for more details about interface.
+//
+// Matches is optional, and provides a set of pre-existing matches to add
+// to the tree.
+// Options are passed to element level processing functions.
+Syntax.Brush.prototype.process = function(text, matches, options) {
 	var top = this.buildTree(text, 0, matches);
 	
 	var lines = top.split(/\n/g);
 	
-	var html = jQuery('<pre class="syntax"></pre>');
+	var html = document.createElement('pre');
+	html.className = 'syntax';
 	
 	for (var i = 0; i < lines.length; i += 1) {
 		var line = lines[i].reduce(null, function (container, match) {
 			if (match.expression) {
 				if (match.expression.process) {
-					container = match.expression.process(container, match);
+					container = match.expression.process(container, match, options);
 				}
 				
-				var process = match.expression.owner.processes[match.expression.klass];
-				if (process) {
-					container = process(container, match);
+				if (match.expression.owner) {
+					var process = match.expression.owner.processes[match.expression.klass];
+					if (process) {
+						container = process(container, match, options);
+					}
 				}
 			}
 			return container;
 		});
 		
-		html.append(line);
+		html.appendChild(line);
 	}
 	
 	return html;
 };
 
+// Highlights a given block of text with a given set of options.
+// options.brush should specify the brush to use, either by direct reference
+// or name.
+// Callback will be called with (highlighted_html, brush_used, original_text, options)
+Syntax.highlightText = function(text, options, callback) {
+	var brushName = (options.brush || 'plain').toLowerCase();
+	
+	brushName = Syntax.aliases[brushName] || brushName;
+	
+	Syntax.brushes.get(brushName, function(brush) {
+		if (options.tabWidth) {
+			// Calculate the tab expansion and offsets
+			replacement = Syntax.convertTabsToSpaces(text, options.tabWidth);
+			
+			// Update any existing matches
+			if (options.matches && options.matches.length) {
+				var linearOffsets = Syntax.convertToLinearOffsets(replacement.offsets, text.length);
+				options.matches = Syntax.updateMatchesWithOffsets(options.matches, linearOffsets, replacement.text);
+			}
+			
+			text = replacement.text;
+		}
+		
+		var html = brush.process(text, options.matches, options);
+		
+		if (options.linkify !== false) {
+			jQuery('span.href', html).each(function(){
+				jQuery(this).replaceWith(jQuery('<a>').attr('href', this.innerHTML).text(this.innerHTML));
+			});
+		}
+		
+		callback(html, brush, text, options);
+	});
+}
+
+// Highlight a given set of elements with a set of options.
+// Callback will be called once per element with (options, highlighted_html, original_container)
 Syntax.highlight = function (elements, options, callback) {
 	if (typeof(options) === 'function') {
 		callback = options;
@@ -903,6 +1109,7 @@ Syntax.highlight = function (elements, options, callback) {
 	}
 	
 	options.layout = options.layout || 'preformatted';
+	options.matches = [];
 	
 	if (typeof(options.tabWidth) === 'undefined') {
 		options.tabWidth = 4;
@@ -911,56 +1118,44 @@ Syntax.highlight = function (elements, options, callback) {
 	elements.each(function () {
 		var container = jQuery(this);
 		
-		// We can augment the plain text to extract existing annotations.
-		var matches = Syntax.extractElementMatches(container);
-		var text = Syntax.getCDATA(container);
+		// We can augment the plain text to extract existing annotations (e.g. <span class="foo">...</span>).
+		options.matches = options.matches.concat(Syntax.extractElementMatches(container));
+		
+		var text = Syntax.innerText(this);
 		
 		var match = text.match(/-\*- mode: (.+?);(.*?)-\*-/i);
 		var endOfSecondLine = text.indexOf("\n", text.indexOf("\n") + 1);
-		
+
 		if (match && match.index < endOfSecondLine) {
 			options.brush = options.brush || match[1];
 			var modeline = match[2];
-			
+
 			var mode = /([a-z\-]+)\:(.*?)\;/gi;
-			
+
 			while((match = mode.exec(modeline)) !== null) {
 				var setter = Syntax.modeLineOptions[match[1]];
-				
+
 				if (setter) {
 					setter(match[1], match[2], options);
 				}
 			}
 		}
 		
-		var brushName = (options.brush || 'plain').toLowerCase();
-		
-		brushName = Syntax.aliases[brushName] || brushName;
-		
-		Syntax.brushes.get(brushName, function(brush) {
-			if (options.tabWidth) {
-				// Calculate the tab expansion and offsets
-				replacement = Syntax.convertTabsToSpaces(text, options.tabWidth);
-				
-				// Update any existing matches
-				if (matches && matches.length) {
-					var linearOffsets = Syntax.convertToLinearOffsets(replacement.offsets, text.length);
-					matches = Syntax.updateMatchesWithOffsets(matches, linearOffsets, replacement.text);
-				}
-				
-				text = replacement.text;
-			}
-			
-			var html = brush.process(text, matches);
-			
-			if (options.linkify !== false) {
-				jQuery('span.href', html).each(function(){
-					jQuery(this).replaceWith(jQuery('<a>').attr('href', this.innerHTML).text(this.innerHTML));
-				});
-			}
-			
+		Syntax.highlightText(text, options, function(html, brush/*, text, options*/) {
 			Syntax.layouts.get(options.layout, function(layout) {
-				html = layout(options, html, container);
+				html = layout(options, $(html), $(container));
+
+				// If there is a theme specified, ensure it is added to the top level class.
+				if (options.theme) {
+					// Load dependencies
+					var themes = Syntax.themes[options.theme];
+					for (var i = 0; i < themes.length; i += 1) {
+						html.addClass("syntax-theme-" + themes[i]);
+					}
+
+					// Add the base theme
+					html.addClass("syntax-theme-" + options.theme);
+				}
 
 				if (brush.postprocess) {
 					html = brush.postprocess(options, html, container);
diff --git a/js/jquery.syntax.js b/js/jquery.syntax.js
index 86d54e0..1325ea0 100644
--- a/js/jquery.syntax.js
+++ b/js/jquery.syntax.js
@@ -68,7 +68,7 @@ ResourceLoader.prototype._loaded = function (name) {
 	this.loading[name] = null;
 
 	if (!resource) {
-		alert("Could not load resource named " + name);
+		alert("ResourceLoader: Could not load resource named " + name);
 	} else {
 		for (var i = 0; i < loading.length; i += 1) {
 			loading[i](resource);
@@ -108,10 +108,12 @@ var Syntax = {
 	root: null, 
 	aliases: {},
 	styles: {},
+	themes: {},
 	lib: {},
 	defaultOptions: {
 		cacheScripts: true,
-		cacheStyleSheets: true
+		cacheStyleSheets: true,
+		theme: "base"
 	},
 	
 	brushes: new ResourceLoader(function (name, callback) {
@@ -170,9 +172,12 @@ var Syntax = {
 	
 	getResource: function (prefix, name, callback) {
 		var basename = prefix + "." + name;
+		var styles = this.styles[basename];
 		
-		if (this.styles[basename]) {
-			this.getStyles(this.root + this.styles[basename]);
+		if (styles) {
+			for (var i = 0; i < styles.length; i += 1) {
+				this.getStyles(this.root + styles[i]);
+			}
 		}
 		
 		Syntax.getScript(this.root + basename + '.js', callback);
@@ -256,10 +261,10 @@ var Syntax = {
 	},
 	
 	log: function() {
-		if (console && console.log) {
+		if (typeof(console) != "undefined" && console.log) {
 			console.log.apply(console, arguments);
-		} else {
-			alert(arguments.join(" "));
+		} else if (window.console && window.console.log) {
+			window.console.log.apply(window.console, arguments);
 		}
 	}
 };
diff --git a/js/jquery.syntax.layout.editor.js b/js/jquery.syntax.layout.editor.js
new file mode 100644
index 0000000..0a8c009
--- /dev/null
+++ b/js/jquery.syntax.layout.editor.js
@@ -0,0 +1,291 @@
+//	This file is part of the "jQuery.Syntax" project, and is distributed under the MIT License.
+//	Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
+//	See <jquery.syntax.js> for licensing details.
+
+Syntax.Editor = function(container, text) {
+	this.container = container;
+	this.current = this.getLines();
+}
+
+// This function generates an array of accumulated line offsets e.g.
+// If line 8 is actually in child element 6, indices[8] = -2
+Syntax.Editor.prototype.getLines = function() {
+	var children = this.container.childNodes, lines = [], offsets = [];
+	
+	// Sometimes, e.g. when deleting text, children elements are not complete lines.
+	// We need to accumulate incomplete lines (1), and then append them to the 
+	// start of the next complete line (2)
+	var text = "", startChild = 0;
+	for (var i = 0; i < children.length; i += 1) {
+		var childLines = Syntax.innerText([children[i]]).split('\n');
+		
+		if (childLines.length > 1) {
+			childLines[0] = text + childLines[0]; // (2)
+			text = childLines.pop();
+		} else {
+			text += childLines[0]; // (1)
+			continue;
+		}
+		
+		for (var j = 0; j < childLines.length; j += 1) {
+			offsets.push(startChild - lines.length);
+			lines.push(childLines[j]);
+		}
+		
+		startChild = i + 1;
+	}
+	
+	// Final line, any remaining text
+	if (text != "") {
+		offsets.push(startChild - lines.length);
+		lines.push(text);
+	} else {
+		startChild -= 1;
+	}
+	
+	offsets.push(startChild);
+	
+	Syntax.log("getLines", offsets, lines, children);
+	
+	return {lines: lines, offsets: offsets};
+}
+
+// This function updates the editor's internal state with regards to lines changed.
+// This can be lines added, removed or modified partially. This function returns
+// a list of lines which are different between the previous set of lines and the
+// updated set of lines.
+// This algorithm is not a general diff algorithm because we expect three cases only:
+//		1: A single line was modified (most common case)
+//		2: Some lines were removed (selection -> delete)
+//		3: Some lines were added (paste)
+Syntax.Editor.prototype.updateChangedLines = function() {
+	var result = {};
+	
+	var updated = this.getLines();
+	
+	// Find the sequence of lines at the start preceeding the change:
+	var i = 0, j = 0;
+	while (i < this.current.lines.length && j < updated.lines.length) {
+		if (this.current.lines[i] == updated.lines[j]) {
+			i += 1;
+			j += 1;
+		} else {
+			break;
+		}
+	}
+	
+	// The length of the initial segment which hasn't changed:
+	result.start = j;
+	
+	// Find the sequence of lines at the end proceeding the change:
+	i = this.current.lines.length, j = updated.lines.length;
+	while (i > result.start && j > result.start) {
+		if (this.current.lines[i-1] == updated.lines[j-1]) {
+			i -= 1;
+			j -= 1;
+		} else {
+			break;
+		}
+	}
+	
+	// The index of the remaining portion which hasn't changed:
+	result.end = j;
+	// The index to the original set of lines which were the same:
+	result.originalEnd = i;
+	
+	// Did we add or remove some lines?
+	result.difference = updated.lines.length - this.current.lines.length;
+	
+	// This should be augmented to improve the above.
+	while (result.start > 0) {
+		if (updated.offsets[result.start] == updated.offsets[result.start-1])
+			break;
+		
+		result.start -= 1;
+	}
+	
+	if (result.difference > 0) {
+		while (result.end < (updated.lines.length-1)) {
+			if (updated.offsets[result.end-1] == updated.offsets[result.end])
+				break;
+			
+			result.end += 1;
+			result.originalEnd += 1;
+		}
+	}
+	
+	// Update the internal state for the next update.
+	this.current = updated;
+	this.changed = result;
+	
+	return result;
+}
+
+Syntax.Editor.prototype.textForLines = function(start, end) {
+	return this.current.lines.slice(start, end).join('\n') + '\n';
+}
+
+Syntax.Editor.prototype.updateLines = function(changed, newLines) {
+	// We have two cases to handle, either we are replacing lines
+	//	(1a) Replacing old lines with one more more new lines (update)
+	//	(1b) Replacing old lines with zero new lines (removal)
+	// Or we are inserting lines
+	//	(2a) We are inserting lines at the start of the element
+	//	(2b) We are inserting lines after an existing element.
+	
+	if (changed.start != changed.end) {
+		// When text is deleted, at most two elements can remain:
+		//	(1) Whatever was partially remaining on the first line.
+		//	(2) Whatever was partially remaining on the last line.
+		// All other lines have already been removed by the container.
+		// changed.difference tells us how many elements have already been removed.
+		
+		// Cases (1a) and (1b)
+		var start = changed.start, end = changed.end;
+		
+		start += this.current.offsets[start];
+		end += this.current.offsets[end];
+		
+		var oldLines = Array.prototype.slice.call(this.container.childNodes, start, end);
+		
+		$(oldLines).replaceWith(newLines);
+	} else {
+		if (changed.start == 0)
+			$(this.container).prepend(newLines);
+		else {
+			var start = changed.start;
+			
+			start += this.current.offsets[start];
+			
+			$(this.container.childNodes[start]).after(newLines);
+		}
+	}
+}
+
+// http://jsfiddle.net/TjXEG/1/
+Syntax.Editor.getCharacterOffset = function(element) {
+	var caretOffset = 0;
+	if (typeof window.getSelection != "undefined") {
+		var range = window.getSelection().getRangeAt(0);
+		var preCaretRange = range.cloneRange();
+		preCaretRange.selectNodeContents(element);
+		preCaretRange.setEnd(range.endContainer, range.endOffset);
+		caretOffset = preCaretRange.toString().length;
+	} else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
+		var textRange = document.selection.createRange();
+		var preCaretTextRange = document.body.createTextRange();
+		preCaretTextRange.moveToElementText(element);
+		preCaretTextRange.setEndPoint("EndToEnd", textRange);
+		caretOffset = preCaretTextRange.text.length;
+	}
+	return caretOffset;
+};
+
+Syntax.Editor.getNodesForCharacterOffsets = function(offsets, node) {
+	var treeWalker = document.createTreeWalker(
+		node,
+		NodeFilter.SHOW_TEXT,
+		function(node) {
+			return NodeFilter.FILTER_ACCEPT;
+		},
+		false
+	);
+	
+	var nodes = [], charCount = 0, i = 0;
+	while (i < offsets.length && treeWalker.nextNode()) {
+		var end = charCount + treeWalker.currentNode.length;
+
+		while (i < offsets.length && offsets[i] < end) {
+			nodes.push([treeWalker.currentNode, charCount, end]);
+
+			i += 1;
+		}
+
+		charCount = end;
+	}
+	
+	return nodes;
+};
+
+Syntax.Editor.prototype.getClientState = function() {
+	var state = {};
+	
+	var selection = window.getSelection();
+	
+	if (selection.rangeCount > 0)
+		state.range = selection.getRangeAt(0);
+	
+	if (state.range) {
+		state.startOffset = Syntax.Editor.getCharacterOffset(this.container);
+	}
+	
+	return state;
+};
+
+Syntax.Editor.prototype.setClientState = function(state) {
+	if (state.startOffset) {
+		var nodes = Syntax.Editor.getNodesForCharacterOffsets([state.startOffset], this.container);
+		
+		var range = document.createRange();
+		range.setStart(nodes[0][0], state.startOffset - nodes[0][1]);
+		range.setEnd(nodes[0][0], state.startOffset - nodes[0][1]);
+		
+		var selection = window.getSelection();
+		selection.removeAllRanges();
+		selection.addRange(range);
+	}
+};
+
+Syntax.layouts.editor = function(options, code/*, container*/) {
+	var container = jQuery('<div class="editor syntax highlighted" contentEditable="true">');
+	
+	container.append(code.children());
+	
+	var editor = new Syntax.Editor(container.get(0));
+		
+	var updateContainer = function(lineHint) {
+		// Need to save cursor position/selection
+		var clientState = editor.getClientState();
+		var changed = editor.updateChangedLines();
+		
+		// Sometimes there are problems where multiple spans exist on the same line.
+		if (changed.difference < 0 && changed.start > 0)
+			changed.start -= 1;
+		
+		var text = editor.textForLines(changed.start, changed.end);
+		
+		if (changed.start == changed.end) {
+			editor.updateLines(changed, []);
+		} else {
+			// Lines have been added, update the highlighting.
+			Syntax.highlightText(text, options, function(html) {
+				editor.updateLines(changed, html.children().get());
+			
+				// Restore cusor position/selection if possible
+				editor.setClientState(clientState);
+			});
+		}
+	};
+	
+	// 'blur keyup paste mouseup'
+	container.bind('keyup', function(){
+		updateContainer();
+	});
+	
+	container.bind('paste', function(event){
+		updateContainer();
+	});
+	
+	container.bind('keydown', function(event){
+		if (event.keyCode == 9) {
+			event.preventDefault();
+			document.execCommand('insertHTML', false, "    ");
+		}
+		else if (event.keyCode == 13) {
+			event.preventDefault();
+			document.execCommand('insertHTML', false, "\n");
+		}
+	});
+	
+	return jQuery('<div class="syntax-container">').append(container);
+};
diff --git a/js/jquery.syntax.layout.inline.js b/js/jquery.syntax.layout.inline.js
index f44c70b..4a94350 100644
--- a/js/jquery.syntax.layout.inline.js
+++ b/js/jquery.syntax.layout.inline.js
@@ -4,8 +4,10 @@
 
 Syntax.layouts.inline = function(options, code, container) {
 	var inline = jQuery('<code class="syntax highlighted"></code>');
-	
 	inline.append(code.children());
 	
-	return inline;
+	var container = jQuery('<span class="syntax-container">');
+	container.append(inline);
+	
+	return container;
 };
diff --git a/xslt/common/html.xsl b/xslt/common/html.xsl
index c8af360..9964f2b 100644
--- a/xslt/common/html.xsl
+++ b/xslt/common/html.xsl
@@ -1896,7 +1896,8 @@ is included by *{html.js.syntax}.
   <xsl:if test="$html.syntax.highlight">
 <xsl:text><![CDATA[
 $(document).ready( function () { jQuery.syntax({root: ']]></xsl:text>
-<xsl:value-of select="$html.js.root"/><xsl:text><![CDATA[', blockLayout: 'yelp'}); });
+<xsl:value-of select="$html.js.root"/><xsl:text><![CDATA[', blockLayout: 'yelp',
+theme: false, linkify: false}); });
 ]]></xsl:text>
   </xsl:if>
 </xsl:template>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]