diff options
author | Shaun McCance <shaunm@gnome.org> | 2012-03-02 15:59:08 -0500 |
---|---|---|
committer | Shaun McCance <shaunm@gnome.org> | 2012-03-02 15:59:08 -0500 |
commit | 062c0e4e7856e8171d830abf26655e1b496eb02b (patch) | |
tree | 9c36a772e50ee7adbb7bbfcb05f9671268e132b8 | |
parent | f8897aab06fbbea218e1bd7a8b1c666806962a82 (diff) | |
download | yelp-xsl-062c0e4e7856e8171d830abf26655e1b496eb02b.tar.gz |
Updating jquery.syntax to 3.1 git tag for non-linking param
-rw-r--r-- | js/Makefile.am | 9 | ||||
-rw-r--r-- | js/jquery.syntax.brush.bash.js | 5 | ||||
-rw-r--r-- | js/jquery.syntax.brush.clang.js | 13 | ||||
-rw-r--r-- | js/jquery.syntax.brush.css.js | 15 | ||||
-rw-r--r-- | js/jquery.syntax.brush.go.js | 47 | ||||
-rw-r--r-- | js/jquery.syntax.brush.javascript.js | 2 | ||||
-rw-r--r-- | js/jquery.syntax.brush.ocaml.js | 73 | ||||
-rw-r--r-- | js/jquery.syntax.brush.ooc.js | 1 | ||||
-rw-r--r-- | js/jquery.syntax.brush.perl5.js | 2 | ||||
-rw-r--r-- | js/jquery.syntax.brush.php-script.js | 6 | ||||
-rw-r--r-- | js/jquery.syntax.brush.protobuf.js | 43 | ||||
-rw-r--r-- | js/jquery.syntax.brush.ruby.js | 15 | ||||
-rw-r--r-- | js/jquery.syntax.brush.scala.js | 44 | ||||
-rw-r--r-- | js/jquery.syntax.brush.super-collider.js | 57 | ||||
-rw-r--r-- | js/jquery.syntax.brush.xml.js | 52 | ||||
-rw-r--r-- | js/jquery.syntax.core.js | 487 | ||||
-rw-r--r-- | js/jquery.syntax.js | 19 | ||||
-rw-r--r-- | js/jquery.syntax.layout.editor.js | 291 | ||||
-rw-r--r-- | js/jquery.syntax.layout.inline.js | 6 | ||||
-rw-r--r-- | xslt/common/html.xsl | 3 |
20 files changed, 996 insertions, 194 deletions
diff --git a/js/Makefile.am b/js/Makefile.am index 3478d5f3..6fbd4a0d 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 bf7cc5c5..d08464e6 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 db22bed4..ed30d05b 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 76df7fa1..3a0fc5b5 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> </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(' ')) + 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 00000000..a3764876 --- /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 193c3ee9..d0ad5415 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 00000000..c0b7e205 --- /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 000781b3..133040c0 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 6cce3226..4ec9c4d9 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 fab2ff7f..c053fa02 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 00000000..1ebd8f34 --- /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 93328905..5d37ed1c 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 00000000..544a26b1 --- /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 00000000..4064ce18 --- /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 69a4b470..03bbbf2e 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 4cbd9c9e..ecc71f83 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: /(<|<)!--[\s\S]*?--(>|>)/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 86d54e01..1325ea00 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 00000000..0a8c009a --- /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 f44c70b2..4a943501 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 c8af3602..9964f2b5 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> |