├── README.md ├── doc └── element-events.txt ├── index.html ├── index.less ├── less.js ├── main.js └── prettify.js /README.md: -------------------------------------------------------------------------------- 1 | # webkit-editor 2 | 3 | This is an experiment in creating a text editor (for computer code) in Safari/WebKit. 4 | 5 | **Requires Safari 4 or Chrome** 6 | 7 | > Disclaimer: This is just for fun and not meant to be a useful editor. 8 | 9 | ## Usage 10 | 11 | Open `index.html` or try it out online. 12 | 13 | ### Disabling Prettify (syntax highlighting) 14 | 15 | This thing currently uses [Google Prettify](http://code.google.com/p/google-code-prettify/) for syntax highlighting which is very buggy. You can disable prettify (and thus enable things like undo/redo which breaks with Prettify) by changing the following line in `index.html`: 16 | 17 | 18 | 19 | to: 20 | 21 | 22 | 23 | ## MIT license 24 | 25 | Copyright (c) 2010 Rasmus Andersson 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a copy 28 | of this software and associated documentation files (the "Software"), to deal 29 | in the Software without restriction, including without limitation the rights 30 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 31 | copies of the Software, and to permit persons to whom the Software is 32 | furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in 35 | all copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 43 | THE SOFTWARE. 44 | -------------------------------------------------------------------------------- /doc/element-events.txt: -------------------------------------------------------------------------------- 1 | abort 2 | beforecopy 3 | beforecut 4 | beforepaste 5 | blur 6 | change 7 | click 8 | contextmenu 9 | copy 10 | cut 11 | dblclick 12 | drag 13 | dragend 14 | dragenter 15 | dragleave 16 | dragover 17 | dragstart 18 | drop 19 | error 20 | focus 21 | input 22 | invalid 23 | keydown 24 | keypress 25 | keyup 26 | load 27 | mousedown 28 | mousemove 29 | mouseout 30 | mouseover 31 | mouseup 32 | mousewheel 33 | paste 34 | reset 35 | scroll 36 | search 37 | select 38 | selectstart 39 | submit -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Editor 6 | 8 | 9 | 10 | 15 | 16 | 17 | 18 |
19 |

Euhm

20 |

21 | I'm sorry, but this little experiment only functions properly in 22 | Safari 4 or Chrome. 23 |

24 |

25 | Try it anyway 26 |

27 |
28 | 29 | *Example* 30 | 31 | 32 | 33 | 1 34 | 35 |
var sys = require('sys');
 36 | var events = require("events"),
 37 |     Buffer = require('buffer').Buffer;
 38 | 
 39 | // execute fn (in the local process), finalized by invocation of callback
 40 | function execute(ctx, fn, callback) {
 41 |   var result;
 42 |   try {
 43 |     result = fn(ctx, callback);
 44 |   } catch (err) {
 45 |     // fn caused some trouble
 46 |     return callback(err);
 47 |   }
 48 |   if (result === callback || result === exports.LATER) {
 49 |     // fn is async and will invoke callback when done
 50 |     return callback;
 51 |   } else if (result === undefined) {
 52 |     // undefined (no) return value means the context is to be returned
 53 |     result = ctx;
 54 |   }
 55 |   callback(null, result);
 56 | }
 57 | 
 58 | exports.LATER = 1;
 59 | 
 60 | if (!Array.prototype.map) {
 61 |   Array.prototype.map = function(fun /*, thisp*/) {
 62 |     var len = this.length >>> 0;
 63 |     var res = new Array(len);
 64 |     var thisp = arguments[1];
 65 | 
 66 |     for (var i = 0; i < len; i++) {
 67 |       if (i in this) {
 68 |         res[i] = fun.call(thisp, this[i], i, this);
 69 |       }
 70 |     }
 71 |     return res;
 72 |   };
 73 | }
 74 | if (!Array.prototype.filter) {
 75 |   Array.prototype.filter = function (block /*, thisp */) {
 76 |     var values = [];
 77 |     var thisp = arguments[1];
 78 |     for (var i = 0; i < this.length; i++) {
 79 |       if (block.call(thisp, this[i])) {
 80 |         values.push(this[i]);
 81 |       }
 82 |     }
 83 |     return values;
 84 |   };
 85 | }
 86 | 
87 |
88 | 89 | → 90 | webkit-editor 91 | 92 | 135 | 146 | 147 | -------------------------------------------------------------------------------- /index.less: -------------------------------------------------------------------------------- 1 | body { 2 | background-color:#242424; color:#444; 3 | padding:0; margin:0; 4 | font-family:helvetica,sans-serif; font-size:13px; 5 | text-rendering: optimizelegibility; 6 | } 7 | img { border:none; } 8 | a { text-decoration:none; } 9 | h1 > a, h2 > a, h3 > a, h4 > a, h5 > a, h6 > a { color:inherit; } 10 | // ----------------------------------------------------------------------------- 11 | @font-face { 12 | font-family: 'M+ 1m'; 13 | font-style: normal; 14 | font-weight: normal; 15 | src: local('mplus-1m-light'), local('M+ 1m light'), 16 | url('http://hunch.se/style/fonts/mplus-1m-light.ttf') format('truetype'); 17 | } 18 | @font-face { 19 | font-family: 'M+ 1m'; 20 | font-style: normal; 21 | font-weight: bold; 22 | src: local('mplus-1m-medium'), local('M+ 1m medium'), 23 | url('http://hunch.se/style/fonts/mplus-1m-medium.ttf') format('truetype'); 24 | } 25 | // ----------------------------------------------------------------------------- 26 | // templates/mixins 27 | .rect (@left: auto, @top: auto, @right: auto, @bottom: auto) { 28 | display:block; position:absolute; 29 | left:@left; top:@top; right:@right; bottom:@bottom; 30 | } 31 | // ----------------------------------------------------------------------------- 32 | body { overflow:hidden; } 33 | block { display:block; position:absolute; } 34 | .less-error-message { z-index:9999; } 35 | 36 | @font_size: 13px; 37 | @line_height: @font_size * 1.4; 38 | @gutter_width: 30px; 39 | @top_margin: 5px; 40 | 41 | #header { 42 | left:0; top:0; right:0; 43 | height:20px; z-index:2; 44 | background-color:#999; color:#000; 45 | border-bottom:1px solid #111; 46 | line-height:20px; 47 | padding:0 1em; 48 | } 49 | 50 | .debugbg (@r:255, @g:0, @b:0) { 51 | //background-color:rgba(@r, @g, @b, 0.1) 52 | } 53 | 54 | .selection-background { background-color: rgba(100,140,200,0.4); } 55 | 56 | window { 57 | .rect(0, 20px, 0, 20px); 58 | overflow:auto; 59 | font-family:'M+ 1m', monospace; 60 | font-size: @font_size; 61 | line-height: @line_height; 62 | .debugbg(0,255); 63 | 64 | gutter { 65 | width: @gutter_width; 66 | .rect(0, @top_margin, 0, 0); 67 | .debugbg(255,255); 68 | line-numbers { 69 | @left_margin: 2px; 70 | max-width: @gutter_width - @left_margin; 71 | .rect(@left_margin, 0, auto, 0); 72 | white-space:pre; 73 | text-align:right; 74 | .debugbg(255,255); 75 | } 76 | } 77 | pre { 78 | display:block; 79 | margin-top:@top_margin; margin-left:@gutter_width; 80 | white-space:pre; 81 | background-color:#202020; 82 | color:#ccc; 83 | font-family:'M+ 1m', monospace; 84 | text-shadow:#000 0 1px 0; 85 | outline:none; 86 | padding-bottom:2em; 87 | margin-bottom:-1em; 88 | ::selection { .selection-background; text-shadow:none; } 89 | //.debugbg(); 90 | //word-wrap: break-word; // could be char 91 | /* 92 | -webkit-line-break: after-white-space; 93 | -webkit-nbsp-mode: space; 94 | -webkit-user-modify: read-write; 95 | */ 96 | // links 97 | a { text-decoration:underline; color:inherit; } 98 | // "invisible" marks 99 | imark { 100 | color:rgba(255,255,255, 0.2); 101 | text-shadow: none; 102 | } 103 | // prettify style 104 | .pln { } /* plain */ 105 | .str { color:#86ad6c; background-color:#1e2620; } /* strings */ 106 | .kwd { color: #5ecefc; } /* keyword */ 107 | .com { color: #777; } /* comments */ 108 | .com::selection { color: #999; .selection-background; } 109 | .typ { color: lightsalmon; } /* type or constant */ 110 | .lit { color: #f57aab; } /* literals */ 111 | .pun { color: #ddd; } /* punctuation */ 112 | .tag { color: #dfc489; } /* tag */ 113 | .atn { color: #ac875b; } /* attribute name */ 114 | .atv { color: #899a95; } /* attribute value */ 115 | .dec { color: #ca773f; } /* declaration */ 116 | } 117 | pre.lang-txt { 118 | b,em { color:#fff; } 119 | samp { 120 | font-family:'M+ 1m', monospace; 121 | background-color:#202933; 122 | color:#ace; 123 | } 124 | } 125 | pre.toplayer { 126 | background-color:transparent; 127 | text-shadow:none; 128 | color: rgba(255,255,255, 0.2); 129 | z-index:2; 130 | .rect(0,0,0,0); 131 | } 132 | } 133 | 134 | #minibuffer { 135 | left:0; bottom:0; right:0; 136 | height:20px; z-index:2; 137 | background-color:#111; 138 | border-top:2px solid rgba(255,255,255, 0.2); 139 | line-height:20px; 140 | padding:0 1em; 141 | input[type=text] { 142 | .rect(30px, 0, 100px, 0); 143 | border:none; 144 | border-right:1px solid #222; 145 | background:transparent; 146 | color:inherit; 147 | padding:0; margin:0; 148 | font:inherit; 149 | height:20px; 150 | line-height:20px; 151 | } 152 | input[type=text]:focus { 153 | color:#ddd; 154 | outline:none; 155 | } 156 | input[type=text].highlight { 157 | color:red; 158 | } 159 | a { 160 | float:right; 161 | color:#999; 162 | text-shadow:#000 0 1px 0; 163 | } 164 | a:hover { color:#5ecefc; } 165 | } 166 | 167 | point-mark { display:none; } 168 | /*point-mark { 169 | display:inline-block; 170 | -webkit-box-align: start; 171 | margin-bottom: @font_size - @line_height; 172 | height:@line_height; 173 | width:2px; 174 | background-color:red; 175 | } 176 | point-mark.end { background-color:green; }*/ 177 | 178 | 179 | #compat-disclaimer { 180 | display:none; 181 | margin:2em auto; 182 | width:70%; 183 | color:#ddd; 184 | a { color:#5ecefc; } 185 | a:hover { text-decoration:underline; } 186 | } 187 | 188 | 189 | -------------------------------------------------------------------------------- /less.js: -------------------------------------------------------------------------------- 1 | // 2 | // LESS - Leaner CSS v1.0.33 3 | // http://lesscss.org 4 | // 5 | // Copyright (c) 2010, Alexis Sellier 6 | // Licensed under the Apache 2.0 License. 7 | // 8 | (function (window, undefined) { 9 | // 10 | // Stub out `require` in the browser 11 | // 12 | function require(arg) { 13 | return window.less[arg.split('/')[1]]; 14 | }; 15 | 16 | 17 | // ecma-5.js 18 | // 19 | // -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License 20 | // -- tlrobinson Tom Robinson 21 | // dantman Daniel Friesen 22 | 23 | // 24 | // Array 25 | // 26 | if (!Array.isArray) { 27 | Array.isArray = function(obj) { 28 | return Object.prototype.toString.call(obj) === "[object Array]" || 29 | (obj instanceof Array); 30 | }; 31 | } 32 | if (!Array.prototype.forEach) { 33 | Array.prototype.forEach = function(block, thisObject) { 34 | var len = this.length >>> 0; 35 | for (var i = 0; i < len; i++) { 36 | if (i in this) { 37 | block.call(thisObject, this[i], i, this); 38 | } 39 | } 40 | }; 41 | } 42 | if (!Array.prototype.map) { 43 | Array.prototype.map = function(fun /*, thisp*/) { 44 | var len = this.length >>> 0; 45 | var res = new Array(len); 46 | var thisp = arguments[1]; 47 | 48 | for (var i = 0; i < len; i++) { 49 | if (i in this) { 50 | res[i] = fun.call(thisp, this[i], i, this); 51 | } 52 | } 53 | return res; 54 | }; 55 | } 56 | if (!Array.prototype.filter) { 57 | Array.prototype.filter = function (block /*, thisp */) { 58 | var values = []; 59 | var thisp = arguments[1]; 60 | for (var i = 0; i < this.length; i++) { 61 | if (block.call(thisp, this[i])) { 62 | values.push(this[i]); 63 | } 64 | } 65 | return values; 66 | }; 67 | } 68 | if (!Array.prototype.reduce) { 69 | Array.prototype.reduce = function(fun /*, initial*/) { 70 | var len = this.length >>> 0; 71 | var i = 0; 72 | 73 | // no value to return if no initial value and an empty array 74 | if (len === 0 && arguments.length === 1) throw new TypeError(); 75 | 76 | if (arguments.length >= 2) { 77 | var rv = arguments[1]; 78 | } else { 79 | do { 80 | if (i in this) { 81 | rv = this[i++]; 82 | break; 83 | } 84 | // if array contains no values, no initial value to return 85 | if (++i >= len) throw new TypeError(); 86 | } while (true); 87 | } 88 | for (; i < len; i++) { 89 | if (i in this) { 90 | rv = fun.call(null, rv, this[i], i, this); 91 | } 92 | } 93 | return rv; 94 | }; 95 | } 96 | if (!Array.prototype.indexOf) { 97 | Array.prototype.indexOf = function (value /*, fromIndex */ ) { 98 | var length = this.length; 99 | var i = arguments[1] || 0; 100 | 101 | if (!length) return -1; 102 | if (i >= length) return -1; 103 | if (i < 0) i += length; 104 | 105 | for (; i < length; i++) { 106 | if (!Object.prototype.hasOwnProperty.call(this, i)) { continue } 107 | if (value === this[i]) return i; 108 | } 109 | return -1; 110 | }; 111 | } 112 | 113 | // 114 | // Object 115 | // 116 | if (!Object.keys) { 117 | Object.keys = function (object) { 118 | var keys = []; 119 | for (var name in object) { 120 | if (Object.prototype.hasOwnProperty.call(object, name)) { 121 | keys.push(name); 122 | } 123 | } 124 | return keys; 125 | }; 126 | } 127 | 128 | // 129 | // String 130 | // 131 | if (!String.prototype.trim) { 132 | String.prototype.trim = function () { 133 | return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 134 | }; 135 | } 136 | var less, tree; 137 | 138 | if (typeof(window) === 'undefined') { 139 | less = exports, 140 | tree = require('less/tree'); 141 | } else { 142 | less = window.less = {}, 143 | tree = window.less.tree = {}; 144 | } 145 | // 146 | // less.js - parser 147 | // 148 | // A relatively straight-forward predictive parser. 149 | // There is no tokenization/lexing stage, the input is parsed 150 | // in one sweep. 151 | // 152 | // To make the parser fast enough to run in the browser, several 153 | // optimization had to be made: 154 | // 155 | // - Matching and slicing on a huge input is often cause of slowdowns. 156 | // The solution is to chunkify the input into smaller strings. 157 | // The chunks are stored in the `chunks` var, 158 | // `j` holds the current chunk index, and `current` holds 159 | // the index of the current chunk in relation to `input`. 160 | // This gives us an almost 4x speed-up. 161 | // 162 | // - In many cases, we don't need to match individual tokens; 163 | // for example, if a value doesn't hold any variables, operations 164 | // or dynamic references, the parser can effectively 'skip' it, 165 | // treating it as a literal. 166 | // An example would be '1px solid #000' - which evaluates to itself, 167 | // we don't need to know what the individual components are. 168 | // The drawback, of course is that you don't get the benefits of 169 | // syntax-checking on the CSS. This gives us a 50% speed-up in the parser, 170 | // and a smaller speed-up in the code-gen. 171 | // 172 | // 173 | // Token matching is done with the `$` function, which either takes 174 | // a terminal string or regexp, or a non-terminal function to call. 175 | // It also takes care of moving all the indices forwards. 176 | // 177 | // 178 | less.Parser = function Parser(env) { 179 | var input, // LeSS input string 180 | i, // current index in `input` 181 | j, // current chunk 182 | temp, // temporarily holds a chunk's state, for backtracking 183 | memo, // temporarily holds `i`, when backtracking 184 | furthest, // furthest index the parser has gone to 185 | chunks, // chunkified input 186 | current, // index of current chunk, in `input` 187 | parser; 188 | 189 | var that = this; 190 | 191 | // This function is called after all files 192 | // have been imported through `@import`. 193 | var finish = function () {}; 194 | 195 | var imports = this.imports = { 196 | paths: env && env.paths || [], // Search paths, when importing 197 | queue: [], // Files which haven't been imported yet 198 | files: {}, // Holds the imported parse trees 199 | push: function (path, callback) { 200 | var that = this; 201 | this.queue.push(path); 202 | 203 | // 204 | // Import a file asynchronously 205 | // 206 | less.Parser.importer(path, this.paths, function (root) { 207 | that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue 208 | that.files[path] = root; // Store the root 209 | 210 | callback(root); 211 | 212 | if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing 213 | }); 214 | } 215 | }; 216 | 217 | function save() { temp = chunks[j], memo = i, current = i } 218 | function restore() { chunks[j] = temp, i = memo, current = i } 219 | 220 | function sync() { 221 | if (i > current) { 222 | chunks[j] = chunks[j].slice(i - current); 223 | current = i; 224 | } 225 | } 226 | // 227 | // Parse from a token, regexp or string, and move forward if match 228 | // 229 | function $(tok) { 230 | var match, args, length, c, index, endIndex, k; 231 | 232 | // 233 | // Non-terminal 234 | // 235 | if (tok instanceof Function) { 236 | return tok.call(parser.parsers); 237 | // 238 | // Terminal 239 | // 240 | // Either match a single character in the input, 241 | // or match a regexp in the current chunk (chunk[j]). 242 | // 243 | } else if (typeof(tok) === 'string') { 244 | match = input.charAt(i) === tok ? tok : null; 245 | length = 1; 246 | sync (); 247 | 248 | // 1. We move to the next chunk, if necessary. 249 | // 2. Set the `lastIndex` to be relative 250 | // to the current chunk, and try to match in it. 251 | // 3. Make sure we matched at `index`. Because we use 252 | // the /g flag, the match could be anywhere in the 253 | // chunk. We have to make sure it's at our previous 254 | // index, which we stored in [2]. 255 | // 256 | } else { 257 | sync (); 258 | 259 | if (match = tok.exec(chunks[j])) { // 3. 260 | length = match[0].length; 261 | } else { 262 | return null; 263 | } 264 | } 265 | 266 | // The match is confirmed, add the match length to `i`, 267 | // and consume any extra white-space characters (' ' || '\n') 268 | // which come after that. The reason for this is that LeSS's 269 | // grammar is mostly white-space insensitive. 270 | // 271 | if (match) { 272 | mem = i += length; 273 | endIndex = i + chunks[j].length - length; 274 | 275 | while (i < endIndex) { 276 | c = input.charCodeAt(i); 277 | if (! (c === 32 || c === 10 || c === 9)) { break } 278 | i++; 279 | } 280 | chunks[j] = chunks[j].slice(length + (i - mem)); 281 | current = i; 282 | 283 | if (chunks[j].length === 0 && j < chunks.length - 1) { j++ } 284 | 285 | if(typeof(match) === 'string') { 286 | return match; 287 | } else { 288 | return match.length === 1 ? match[0] : match; 289 | } 290 | } 291 | } 292 | 293 | // Same as $(), but don't change the state of the parser, 294 | // just return the match. 295 | function peek(tok) { 296 | if (typeof(tok) === 'string') { 297 | return input.charAt(i) === tok; 298 | } else { 299 | if (tok.test(chunks[j])) { 300 | return true; 301 | } else { 302 | return false; 303 | } 304 | } 305 | } 306 | 307 | this.env = env = env || {}; 308 | 309 | // The optimization level dictates the thoroughness of the parser, 310 | // the lower the number, the less nodes it will create in the tree. 311 | // This could matter for debugging, or if you want to access 312 | // the individual nodes in the tree. 313 | this.optimization = ('optimization' in this.env) ? this.env.optimization : 1; 314 | 315 | this.env.filename = this.env.filename || null; 316 | 317 | // 318 | // The Parser 319 | // 320 | return parser = { 321 | 322 | imports: imports, 323 | // 324 | // Parse an input string into an abstract syntax tree, 325 | // call `callback` when done. 326 | // 327 | parse: function (str, callback) { 328 | var root, start, end, zone, line, lines, buff = [], c, error = null; 329 | 330 | i = j = current = furthest = 0; 331 | chunks = []; 332 | input = str.replace(/\r\n/g, '\n'); 333 | 334 | // Split the input into chunks. 335 | chunks = (function (chunks) { 336 | var j = 0, 337 | skip = /[^"'`\{\}\/]+/g, 338 | comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g, 339 | level = 0, 340 | match, 341 | chunk = chunks[0], 342 | inString; 343 | 344 | for (var i = 0, c, cc; i < input.length; i++) { 345 | skip.lastIndex = i; 346 | if (match = skip.exec(input)) { 347 | if (match.index === i) { 348 | i += match[0].length; 349 | chunk.push(match[0]); 350 | } 351 | } 352 | c = input.charAt(i); 353 | comment.lastIndex = i; 354 | 355 | if (!inString && c === '/') { 356 | cc = input.charAt(i + 1); 357 | if (cc === '/' || cc === '*') { 358 | if (match = comment.exec(input)) { 359 | if (match.index === i) { 360 | i += match[0].length; 361 | chunk.push(match[0]); 362 | c = input.charAt(i); 363 | } 364 | } 365 | } 366 | } 367 | 368 | if (c === '{' && !inString) { level ++; 369 | chunk.push(c); 370 | } else if (c === '}' && !inString) { level --; 371 | chunk.push(c); 372 | chunks[++j] = chunk = []; 373 | } else { 374 | if (c === '"' || c === "'" || c === '`') { 375 | if (! inString) { 376 | inString = c; 377 | } else { 378 | inString = inString === c ? false : inString; 379 | } 380 | } 381 | chunk.push(c); 382 | } 383 | } 384 | if (level > 0) { throw new(Error)("Missing closing '}'") } 385 | 386 | return chunks.map(function (c) { return c.join('') });; 387 | })([[]]); 388 | 389 | // Start with the primary rule. 390 | // The whole syntax tree is held under a Ruleset node, 391 | // with the `root` property set to true, so no `{}` are 392 | // output. The callback is called when the input is parsed. 393 | root = new(tree.Ruleset)([], $(this.parsers.primary)); 394 | root.root = true; 395 | 396 | root.toCSS = (function (evaluate) { 397 | var line, lines, column; 398 | 399 | return function (options, variables) { 400 | var frames = []; 401 | 402 | options = options || {}; 403 | // 404 | // Allows setting variables with a hash, so: 405 | // 406 | // `{ color: new(tree.Color)('#f01') }` will become: 407 | // 408 | // new(tree.Rule)('@color', 409 | // new(tree.Value)([ 410 | // new(tree.Expression)([ 411 | // new(tree.Color)('#f01') 412 | // ]) 413 | // ]) 414 | // ) 415 | // 416 | if (typeof(variables) === 'object' && !Array.isArray(variables)) { 417 | variables = Object.keys(variables).map(function (k) { 418 | var value = variables[k]; 419 | 420 | if (! (value instanceof tree.Value)) { 421 | if (! (value instanceof tree.Expression)) { 422 | value = new(tree.Expression)([value]); 423 | } 424 | value = new(tree.Value)([value]); 425 | } 426 | return new(tree.Rule)('@' + k, value, false, 0); 427 | }); 428 | frames = [new(tree.Ruleset)(null, variables)]; 429 | } 430 | 431 | try { 432 | var css = evaluate.call(this, { frames: frames }) 433 | .toCSS([], { compress: options.compress || false }); 434 | } catch (e) { 435 | lines = input.split('\n'); 436 | line = getLine(e.index); 437 | 438 | for (var n = e.index, column = -1; 439 | n >= 0 && input.charAt(n) !== '\n'; 440 | n--) { column++ } 441 | 442 | throw { 443 | type: e.type, 444 | message: e.message, 445 | filename: env.filename, 446 | index: e.index, 447 | line: typeof(line) === 'number' ? line + 1 : null, 448 | callLine: e.call && (getLine(e.call) + 1), 449 | callExtract: lines[getLine(e.call)], 450 | stack: e.stack, 451 | column: column, 452 | extract: [ 453 | lines[line - 1], 454 | lines[line], 455 | lines[line + 1] 456 | ] 457 | }; 458 | } 459 | if (options.compress) { 460 | return css.replace(/(\s)+/g, "$1"); 461 | } else { 462 | return css; 463 | } 464 | 465 | function getLine(index) { 466 | return index ? (input.slice(0, index).match(/\n/g) || "").length : null; 467 | } 468 | }; 469 | })(root.eval); 470 | 471 | // If `i` is smaller than the `input.length - 1`, 472 | // it means the parser wasn't able to parse the whole 473 | // string, so we've got a parsing error. 474 | // 475 | // We try to extract a \n delimited string, 476 | // showing the line where the parse error occured. 477 | // We split it up into two parts (the part which parsed, 478 | // and the part which didn't), so we can color them differently. 479 | if (i < input.length - 1) { 480 | i = furthest; 481 | lines = input.split('\n'); 482 | line = (input.slice(0, i).match(/\n/g) || "").length + 1; 483 | 484 | for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ } 485 | 486 | error = { 487 | name: "ParseError", 488 | message: "Syntax Error on line " + line, 489 | filename: env.filename, 490 | line: line, 491 | column: column, 492 | extract: [ 493 | lines[line - 2], 494 | lines[line - 1], 495 | lines[line] 496 | ] 497 | }; 498 | } 499 | 500 | if (this.imports.queue.length > 0) { 501 | finish = function () { callback(error, root) }; 502 | } else { 503 | callback(error, root); 504 | } 505 | }, 506 | 507 | // 508 | // Here in, the parsing rules/functions 509 | // 510 | // The basic structure of the syntax tree generated is as follows: 511 | // 512 | // Ruleset -> Rule -> Value -> Expression -> Entity 513 | // 514 | // Here's some LESS code: 515 | // 516 | // .class { 517 | // color: #fff; 518 | // border: 1px solid #000; 519 | // width: @w + 4px; 520 | // > .child {...} 521 | // } 522 | // 523 | // And here's what the parse tree might look like: 524 | // 525 | // Ruleset (Selector '.class', [ 526 | // Rule ("color", Value ([Expression [Color #fff]])) 527 | // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) 528 | // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) 529 | // Ruleset (Selector [Element '>', '.child'], [...]) 530 | // ]) 531 | // 532 | // In general, most rules will try to parse a token with the `$()` function, and if the return 533 | // value is truly, will return a new node, of the relevant type. Sometimes, we need to check 534 | // first, before parsing, that's when we use `peek()`. 535 | // 536 | parsers: { 537 | // 538 | // The `primary` rule is the *entry* and *exit* point of the parser. 539 | // The rules here can appear at any level of the parse tree. 540 | // 541 | // The recursive nature of the grammar is an interplay between the `block` 542 | // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, 543 | // as represented by this simplified grammar: 544 | // 545 | // primary → (ruleset | rule)+ 546 | // ruleset → selector+ block 547 | // block → '{' primary '}' 548 | // 549 | // Only at one point is the primary rule not called from the 550 | // block rule: at the root level. 551 | // 552 | primary: function () { 553 | var node, root = []; 554 | 555 | while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || 556 | $(this.mixin.call) || $(this.comment) || $(this.directive)) 557 | || $(/^[\s\n]+/)) { 558 | node && root.push(node); 559 | } 560 | return root; 561 | }, 562 | 563 | // We create a Comment node for CSS comments `/* */`, 564 | // but keep the LeSS comments `//` silent, by just skipping 565 | // over them. 566 | comment: function () { 567 | var comment; 568 | 569 | if (input.charAt(i) !== '/') return; 570 | 571 | if (input.charAt(i + 1) === '/') { 572 | return new(tree.Comment)($(/^\/\/.*/), true); 573 | } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { 574 | return new(tree.Comment)(comment); 575 | } 576 | }, 577 | 578 | // 579 | // Entities are tokens which can be found inside an Expression 580 | // 581 | entities: { 582 | // 583 | // A string, which supports escaping " and ' 584 | // 585 | // "milky way" 'he\'s the one!' 586 | // 587 | quoted: function () { 588 | var str; 589 | if (input.charAt(i) !== '"' && input.charAt(i) !== "'") return; 590 | 591 | if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) { 592 | return new(tree.Quoted)(str[0], str[1] || str[2]); 593 | } 594 | }, 595 | 596 | // 597 | // A catch-all word, such as: 598 | // 599 | // black border-collapse 600 | // 601 | keyword: function () { 602 | var k; 603 | if (k = $(/^[A-Za-z-]+/)) { return new(tree.Keyword)(k) } 604 | }, 605 | 606 | // 607 | // A function call 608 | // 609 | // rgb(255, 0, 255) 610 | // 611 | // We also try to catch IE's `alpha()`, but let the `alpha` parser 612 | // deal with the details. 613 | // 614 | // The arguments are parsed with the `entities.arguments` parser. 615 | // 616 | call: function () { 617 | var name, args; 618 | 619 | if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return; 620 | 621 | name = name[1].toLowerCase(); 622 | 623 | if (name === 'url') { return null } 624 | else { i += name.length + 1 } 625 | 626 | if (name === 'alpha') { return $(this.alpha) } 627 | 628 | args = $(this.entities.arguments); 629 | 630 | if (! $(')')) return; 631 | 632 | if (name) { return new(tree.Call)(name, args) } 633 | }, 634 | arguments: function () { 635 | var args = [], arg; 636 | 637 | while (arg = $(this.expression)) { 638 | args.push(arg); 639 | if (! $(',')) { break } 640 | } 641 | return args; 642 | }, 643 | literal: function () { 644 | return $(this.entities.dimension) || 645 | $(this.entities.color) || 646 | $(this.entities.quoted); 647 | }, 648 | 649 | // 650 | // Parse url() tokens 651 | // 652 | // We use a specific rule for urls, because they don't really behave like 653 | // standard function calls. The difference is that the argument doesn't have 654 | // to be enclosed within a string, so it can't be parsed as an Expression. 655 | // 656 | url: function () { 657 | var value; 658 | 659 | if (input.charAt(i) !== 'u' || !$(/^url\(/)) return; 660 | value = $(this.entities.quoted) || $(this.entities.variable) || $(/^[-\w%@$\/.&=:;#+?]+/); 661 | if (! $(')')) throw new(Error)("missing closing ) for url()"); 662 | 663 | return new(tree.URL)((value.value || value instanceof tree.Variable) 664 | ? value : new(tree.Anonymous)(value)); 665 | }, 666 | 667 | // 668 | // A Variable entity, such as `@fink`, in 669 | // 670 | // width: @fink + 2px 671 | // 672 | // We use a different parser for variable definitions, 673 | // see `parsers.variable`. 674 | // 675 | variable: function () { 676 | var name, index = i; 677 | 678 | if (input.charAt(i) === '@' && (name = $(/^@[\w-]+/))) { 679 | return new(tree.Variable)(name, index); 680 | } 681 | }, 682 | 683 | // 684 | // A Hexadecimal color 685 | // 686 | // #4F3C2F 687 | // 688 | // `rgb` and `hsl` colors are parsed through the `entities.call` parser. 689 | // 690 | color: function () { 691 | var rgb; 692 | 693 | if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) { 694 | return new(tree.Color)(rgb[1]); 695 | } 696 | }, 697 | 698 | // 699 | // A Dimension, that is, a number and a unit 700 | // 701 | // 0.5em 95% 702 | // 703 | dimension: function () { 704 | var value, c = input.charCodeAt(i); 705 | if ((c > 57 || c < 45) || c === 47) return; 706 | 707 | if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm)?/)) { 708 | return new(tree.Dimension)(value[1], value[2]); 709 | } 710 | }, 711 | 712 | // 713 | // JavaScript code to be evaluated 714 | // 715 | // `window.location.href` 716 | // 717 | javascript: function () { 718 | var str; 719 | 720 | if (input.charAt(i) !== '`') { return } 721 | 722 | if (str = $(/^`([^`]*)`/)) { 723 | return new(tree.JavaScript)(str[1], i); 724 | } 725 | } 726 | }, 727 | 728 | // 729 | // The variable part of a variable definition. Used in the `rule` parser 730 | // 731 | // @fink: 732 | // 733 | variable: function () { 734 | var name; 735 | 736 | if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] } 737 | }, 738 | 739 | // 740 | // A font size/line-height shorthand 741 | // 742 | // small/12px 743 | // 744 | // We need to peek first, or we'll match on keywords and dimensions 745 | // 746 | shorthand: function () { 747 | var a, b; 748 | 749 | if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return; 750 | 751 | if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) { 752 | return new(tree.Shorthand)(a, b); 753 | } 754 | }, 755 | 756 | // 757 | // Mixins 758 | // 759 | mixin: { 760 | // 761 | // A Mixin call, with an optional argument list 762 | // 763 | // #mixins > .square(#fff); 764 | // .rounded(4px, black); 765 | // .button; 766 | // 767 | // The `while` loop is there because mixins can be 768 | // namespaced, but we only support the child and descendant 769 | // selector for now. 770 | // 771 | call: function () { 772 | var elements = [], e, c, args, index = i, s = input.charAt(i); 773 | 774 | if (s !== '.' && s !== '#') { return } 775 | 776 | while (e = $(/^[#.][\w-]+/)) { 777 | elements.push(new(tree.Element)(c, e)); 778 | c = $('>'); 779 | } 780 | $('(') && (args = $(this.entities.arguments)) && $(')'); 781 | 782 | if (elements.length > 0 && ($(';') || peek('}'))) { 783 | return new(tree.mixin.Call)(elements, args, index); 784 | } 785 | }, 786 | 787 | // 788 | // A Mixin definition, with a list of parameters 789 | // 790 | // .rounded (@radius: 2px, @color) { 791 | // ... 792 | // } 793 | // 794 | // Until we have a finer grained state-machine, we have to 795 | // do a look-ahead, to make sure we don't have a mixin call. 796 | // See the `rule` function for more information. 797 | // 798 | // We start by matching `.rounded (`, and then proceed on to 799 | // the argument list, which has optional default values. 800 | // We store the parameters in `params`, with a `value` key, 801 | // if there is a value, such as in the case of `@radius`. 802 | // 803 | // Once we've got our params list, and a closing `)`, we parse 804 | // the `{...}` block. 805 | // 806 | definition: function () { 807 | var name, params = [], match, ruleset, param, value; 808 | 809 | if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || 810 | peek(/^[^{]*(;|})/)) return; 811 | 812 | if (match = $(/^([#.][\w-]+)\s*\(/)) { 813 | name = match[1]; 814 | 815 | while (param = $(this.entities.variable) || $(this.entities.literal) 816 | || $(this.entities.keyword)) { 817 | // Variable 818 | if (param instanceof tree.Variable) { 819 | if ($(':')) { 820 | if (value = $(this.expression)) { 821 | params.push({ name: param.name, value: value }); 822 | } else { 823 | throw new(Error)("Expected value"); 824 | } 825 | } else { 826 | params.push({ name: param.name }); 827 | } 828 | } else { 829 | params.push({ value: param }); 830 | } 831 | if (! $(',')) { break } 832 | } 833 | if (! $(')')) throw new(Error)("Expected )"); 834 | 835 | ruleset = $(this.block); 836 | 837 | if (ruleset) { 838 | return new(tree.mixin.Definition)(name, params, ruleset); 839 | } 840 | } 841 | } 842 | }, 843 | 844 | // 845 | // Entities are the smallest recognized token, 846 | // and can be found inside a rule's value. 847 | // 848 | entity: function () { 849 | return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) || 850 | $(this.entities.call) || $(this.entities.keyword) || $(this.entities.javascript); 851 | }, 852 | 853 | // 854 | // A Rule terminator. Note that we use `peek()` to check for '}', 855 | // because the `block` rule will be expecting it, but we still need to make sure 856 | // it's there, if ';' was ommitted. 857 | // 858 | end: function () { 859 | return $(';') || peek('}'); 860 | }, 861 | 862 | // 863 | // IE's alpha function 864 | // 865 | // alpha(opacity=88) 866 | // 867 | alpha: function () { 868 | var value; 869 | 870 | if (! $(/^opacity=/i)) return; 871 | if (value = $(/^\d+/) || $(this.entities.variable)) { 872 | if (! $(')')) throw new(Error)("missing closing ) for alpha()"); 873 | return new(tree.Alpha)(value); 874 | } 875 | }, 876 | 877 | // 878 | // A Selector Element 879 | // 880 | // div 881 | // + h1 882 | // #socks 883 | // input[type="text"] 884 | // 885 | // Elements are the building blocks for Selectors, 886 | // they are made out of a `Combinator` (see combinator rule), 887 | // and an element name, such as a tag a class, or `*`. 888 | // 889 | element: function () { 890 | var e, t; 891 | 892 | c = $(this.combinator); 893 | e = $(/^[.#:]?[\w-]+/) || $('*') || $(this.attribute) || $(/^\([^)@]+\)/); 894 | 895 | if (e) { return new(tree.Element)(c, e) } 896 | }, 897 | 898 | // 899 | // Combinators combine elements together, in a Selector. 900 | // 901 | // Because our parser isn't white-space sensitive, special care 902 | // has to be taken, when parsing the descendant combinator, ` `, 903 | // as it's an empty space. We have to check the previous character 904 | // in the input, to see if it's a ` ` character. More info on how 905 | // we deal with this in *combinator.js*. 906 | // 907 | combinator: function () { 908 | var match, c = input.charAt(i); 909 | 910 | if (c === '>' || c === '&' || c === '+' || c === '~') { 911 | i++; 912 | while (input.charAt(i) === ' ') { i++ } 913 | return new(tree.Combinator)(c); 914 | } else if (c === ':' && input.charAt(i + 1) === ':') { 915 | i += 2; 916 | while (input.charAt(i) === ' ') { i++ } 917 | return new(tree.Combinator)('::'); 918 | } else if (input.charAt(i - 1) === ' ') { 919 | return new(tree.Combinator)(" "); 920 | } else { 921 | return new(tree.Combinator)(null); 922 | } 923 | }, 924 | 925 | // 926 | // A CSS Selector 927 | // 928 | // .class > div + h1 929 | // li a:hover 930 | // 931 | // Selectors are made out of one or more Elements, see above. 932 | // 933 | selector: function () { 934 | var sel, e, elements = [], c, match; 935 | 936 | while (e = $(this.element)) { 937 | c = input.charAt(i); 938 | elements.push(e) 939 | if (c === '{' || c === '}' || c === ';' || c === ',') { break } 940 | } 941 | 942 | if (elements.length > 0) { return new(tree.Selector)(elements) } 943 | }, 944 | tag: function () { 945 | return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*'); 946 | }, 947 | attribute: function () { 948 | var attr = '', key, val, op; 949 | 950 | if (! $('[')) return; 951 | 952 | if (key = $(/^[a-z-]+/) || $(this.entities.quoted)) { 953 | if ((op = $(/^[|~*$^]?=/)) && 954 | (val = $(this.entities.quoted) || $(/^[\w-]+/))) { 955 | attr = [key, op, val.toCSS ? val.toCSS() : val].join(''); 956 | } else { attr = key } 957 | } 958 | 959 | if (! $(']')) return; 960 | 961 | if (attr) { return "[" + attr + "]" } 962 | }, 963 | 964 | // 965 | // The `block` rule is used by `ruleset` and `mixin.definition`. 966 | // It's a wrapper around the `primary` rule, with added `{}`. 967 | // 968 | block: function () { 969 | var content; 970 | 971 | if ($('{') && (content = $(this.primary)) && $('}')) { 972 | return content; 973 | } 974 | }, 975 | 976 | // 977 | // div, .class, body > p {...} 978 | // 979 | ruleset: function () { 980 | var selectors = [], s, rules, match; 981 | save(); 982 | 983 | if (match = /^([.#: \w-]+)[\s\n]*\{/.exec(chunks[j])) { 984 | i += match[0].length - 1; 985 | selectors = [new(tree.Selector)([new(tree.Element)(null, match[1])])]; 986 | } else { 987 | while (s = $(this.selector)) { 988 | selectors.push(s); 989 | if (! $(',')) { break } 990 | } 991 | if (s) $(this.comment); 992 | } 993 | 994 | if (selectors.length > 0 && (rules = $(this.block))) { 995 | return new(tree.Ruleset)(selectors, rules); 996 | } else { 997 | // Backtrack 998 | furthest = i; 999 | restore(); 1000 | } 1001 | }, 1002 | rule: function () { 1003 | var value, c = input.charAt(i), important; 1004 | save(); 1005 | 1006 | if (c === '.' || c === '#' || c === '&') { return } 1007 | 1008 | if (name = $(this.variable) || $(this.property)) { 1009 | if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) { 1010 | i += match[0].length - 1; 1011 | value = new(tree.Anonymous)(match[1]); 1012 | } else if (name === "font") { 1013 | value = $(this.font); 1014 | } else { 1015 | value = $(this.value); 1016 | } 1017 | important = $(this.important); 1018 | 1019 | if (value && $(this.end)) { 1020 | return new(tree.Rule)(name, value, important, memo); 1021 | } else { 1022 | furthest = i; 1023 | restore(); 1024 | } 1025 | } 1026 | }, 1027 | 1028 | // 1029 | // An @import directive 1030 | // 1031 | // @import "lib"; 1032 | // 1033 | // Depending on our environemnt, importing is done differently: 1034 | // In the browser, it's an XHR request, in Node, it would be a 1035 | // file-system operation. The function used for importing is 1036 | // stored in `import`, which we pass to the Import constructor. 1037 | // 1038 | "import": function () { 1039 | var path; 1040 | if ($(/^@import\s+/) && 1041 | (path = $(this.entities.quoted) || $(this.entities.url)) && 1042 | $(';')) { 1043 | return new(tree.Import)(path, imports); 1044 | } 1045 | }, 1046 | 1047 | // 1048 | // A CSS Directive 1049 | // 1050 | // @charset "utf-8"; 1051 | // 1052 | directive: function () { 1053 | var name, value, rules, types; 1054 | 1055 | if (input.charAt(i) !== '@') return; 1056 | 1057 | if (value = $(this['import'])) { 1058 | return value; 1059 | } else if (name = $(/^@media|@page/)) { 1060 | types = $(/^[^{]+/).trim(); 1061 | if (rules = $(this.block)) { 1062 | return new(tree.Directive)(name + " " + types, rules); 1063 | } 1064 | } else if (name = $(/^@[-a-z]+/)) { 1065 | if (name === '@font-face') { 1066 | if (rules = $(this.block)) { 1067 | return new(tree.Directive)(name, rules); 1068 | } 1069 | } else if ((value = $(this.entity)) && $(';')) { 1070 | return new(tree.Directive)(name, value); 1071 | } 1072 | } 1073 | }, 1074 | font: function () { 1075 | var value = [], expression = [], weight, shorthand, font, e; 1076 | 1077 | while (e = $(this.shorthand) || $(this.entity)) { 1078 | expression.push(e); 1079 | } 1080 | value.push(new(tree.Expression)(expression)); 1081 | 1082 | if ($(',')) { 1083 | while (e = $(this.expression)) { 1084 | value.push(e); 1085 | if (! $(',')) { break } 1086 | } 1087 | } 1088 | return new(tree.Value)(value); 1089 | }, 1090 | 1091 | // 1092 | // A Value is a comma-delimited list of Expressions 1093 | // 1094 | // font-family: Baskerville, Georgia, serif; 1095 | // 1096 | // In a Rule, a Value represents everything after the `:`, 1097 | // and before the `;`. 1098 | // 1099 | value: function () { 1100 | var e, expressions = [], important; 1101 | 1102 | while (e = $(this.expression)) { 1103 | expressions.push(e); 1104 | if (! $(',')) { break } 1105 | } 1106 | 1107 | if (expressions.length > 0) { 1108 | return new(tree.Value)(expressions); 1109 | } 1110 | }, 1111 | important: function () { 1112 | if (input.charAt(i) === '!') { 1113 | return $(/^! *important/); 1114 | } 1115 | }, 1116 | sub: function () { 1117 | var e; 1118 | 1119 | if ($('(') && (e = $(this.expression)) && $(')')) { 1120 | return e; 1121 | } 1122 | }, 1123 | multiplication: function () { 1124 | var m, a, op, operation; 1125 | if (m = $(this.operand)) { 1126 | while ((op = ($('/') || $('*'))) && (a = $(this.operand))) { 1127 | operation = new(tree.Operation)(op, [operation || m, a]); 1128 | } 1129 | return operation || m; 1130 | } 1131 | }, 1132 | addition: function () { 1133 | var m, a, op, operation; 1134 | if (m = $(this.multiplication)) { 1135 | while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) && 1136 | (a = $(this.multiplication))) { 1137 | operation = new(tree.Operation)(op, [operation || m, a]); 1138 | } 1139 | return operation || m; 1140 | } 1141 | }, 1142 | 1143 | // 1144 | // An operand is anything that can be part of an operation, 1145 | // such as a Color, or a Variable 1146 | // 1147 | operand: function () { 1148 | return $(this.sub) || $(this.entities.dimension) || 1149 | $(this.entities.color) || $(this.entities.variable) || 1150 | $(this.entities.call); 1151 | }, 1152 | 1153 | // 1154 | // Expressions either represent mathematical operations, 1155 | // or white-space delimited Entities. 1156 | // 1157 | // 1px solid black 1158 | // @var * 2 1159 | // 1160 | expression: function () { 1161 | var e, delim, entities = [], d; 1162 | 1163 | while (e = $(this.addition) || $(this.entity)) { 1164 | entities.push(e); 1165 | } 1166 | if (entities.length > 0) { 1167 | return new(tree.Expression)(entities); 1168 | } 1169 | }, 1170 | property: function () { 1171 | var name; 1172 | 1173 | if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) { 1174 | return name[1]; 1175 | } 1176 | } 1177 | } 1178 | }; 1179 | }; 1180 | 1181 | if (typeof(window) !== 'undefined') { 1182 | // 1183 | // Used by `@import` directives 1184 | // 1185 | less.Parser.importer = function (path, paths, callback) { 1186 | if (path.charAt(0) !== '/' && paths.length > 0) { 1187 | path = paths[0] + path; 1188 | } 1189 | // We pass `true` as 3rd argument, to force the reload of the import. 1190 | // This is so we can get the syntax tree as opposed to just the CSS output, 1191 | // as we need this to evaluate the current stylesheet. 1192 | loadStyleSheet({ href: path, title: path }, callback, true); 1193 | }; 1194 | } 1195 | 1196 | (function (tree) { 1197 | 1198 | tree.functions = { 1199 | rgb: function (r, g, b) { 1200 | return this.rgba(r, g, b, 1.0); 1201 | }, 1202 | rgba: function (r, g, b, a) { 1203 | var rgb = [r, g, b].map(function (c) { return number(c) }), 1204 | a = number(a); 1205 | return new(tree.Color)(rgb, a); 1206 | }, 1207 | hsl: function (h, s, l) { 1208 | return this.hsla(h, s, l, 1.0); 1209 | }, 1210 | hsla: function (h, s, l, a) { 1211 | h = (number(h) % 360) / 360; 1212 | s = number(s); l = number(l); a = number(a); 1213 | 1214 | var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; 1215 | var m1 = l * 2 - m2; 1216 | 1217 | return this.rgba(hue(h + 1/3) * 255, 1218 | hue(h) * 255, 1219 | hue(h - 1/3) * 255, 1220 | a); 1221 | 1222 | function hue(h) { 1223 | h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); 1224 | if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; 1225 | else if (h * 2 < 1) return m2; 1226 | else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; 1227 | else return m1; 1228 | } 1229 | }, 1230 | hue: function (color) { 1231 | return new(tree.Dimension)(Math.round(color.toHSL().h)); 1232 | }, 1233 | saturation: function (color) { 1234 | return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%'); 1235 | }, 1236 | lightness: function (color) { 1237 | return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%'); 1238 | }, 1239 | alpha: function (color) { 1240 | return new(tree.Dimension)(color.toHSL().a); 1241 | }, 1242 | saturate: function (color, amount) { 1243 | var hsl = color.toHSL(); 1244 | 1245 | hsl.s += amount.value / 100; 1246 | hsl.s = clamp(hsl.s); 1247 | return hsla(hsl); 1248 | }, 1249 | desaturate: function (color, amount) { 1250 | var hsl = color.toHSL(); 1251 | 1252 | hsl.s -= amount.value / 100; 1253 | hsl.s = clamp(hsl.s); 1254 | return hsla(hsl); 1255 | }, 1256 | lighten: function (color, amount) { 1257 | var hsl = color.toHSL(); 1258 | 1259 | hsl.l += amount.value / 100; 1260 | hsl.l = clamp(hsl.l); 1261 | return hsla(hsl); 1262 | }, 1263 | darken: function (color, amount) { 1264 | var hsl = color.toHSL(); 1265 | 1266 | hsl.l -= amount.value / 100; 1267 | hsl.l = clamp(hsl.l); 1268 | return hsla(hsl); 1269 | }, 1270 | spin: function (color, amount) { 1271 | var hsl = color.toHSL(); 1272 | var hue = (hsl.h + amount.value) % 360; 1273 | 1274 | hsl.h = hue < 0 ? 360 + hue : hue; 1275 | 1276 | return hsla(hsl); 1277 | }, 1278 | greyscale: function (color) { 1279 | return this.desaturate(color, new(tree.Dimension)(100)); 1280 | }, 1281 | e: function (str) { 1282 | return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); 1283 | }, 1284 | '%': function (quoted /* arg, arg, ...*/) { 1285 | var args = Array.prototype.slice.call(arguments, 1), 1286 | str = quoted.content; 1287 | 1288 | for (var i = 0; i < args.length; i++) { 1289 | str = str.replace(/%s/, args[i].content) 1290 | .replace(/%[da]/, args[i].toCSS()); 1291 | } 1292 | str = str.replace(/%%/g, '%'); 1293 | return new(tree.Quoted)('"' + str + '"', str); 1294 | } 1295 | }; 1296 | 1297 | function hsla(hsla) { 1298 | return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a); 1299 | } 1300 | 1301 | function number(n) { 1302 | if (n instanceof tree.Dimension) { 1303 | return parseFloat(n.unit == '%' ? n.value / 100 : n.value); 1304 | } else if (typeof(n) === 'number') { 1305 | return n; 1306 | } else { 1307 | throw { 1308 | error: "RuntimeError", 1309 | message: "color functions take numbers as parameters" 1310 | }; 1311 | } 1312 | } 1313 | 1314 | function clamp(val) { 1315 | return Math.min(1, Math.max(0, val)); 1316 | } 1317 | 1318 | })(require('less/tree')); 1319 | (function (tree) { 1320 | 1321 | tree.Alpha = function (val) { 1322 | this.value = val; 1323 | }; 1324 | tree.Alpha.prototype = { 1325 | toCSS: function () { 1326 | return "alpha(opacity=" + 1327 | (this.value.toCSS ? this.value.toCSS() : this.value) + ")"; 1328 | }, 1329 | eval: function () { return this } 1330 | }; 1331 | 1332 | })(require('less/tree')); 1333 | (function (tree) { 1334 | 1335 | tree.Anonymous = function (string) { 1336 | this.value = string.content || string; 1337 | }; 1338 | tree.Anonymous.prototype = { 1339 | toCSS: function () { 1340 | return this.value; 1341 | }, 1342 | eval: function () { return this } 1343 | }; 1344 | 1345 | })(require('less/tree')); 1346 | (function (tree) { 1347 | 1348 | // 1349 | // A function call node. 1350 | // 1351 | tree.Call = function (name, args) { 1352 | this.name = name; 1353 | this.args = args; 1354 | }; 1355 | tree.Call.prototype = { 1356 | // 1357 | // When evaluating a function call, 1358 | // we either find the function in `tree.functions` [1], 1359 | // in which case we call it, passing the evaluated arguments, 1360 | // or we simply print it out as it appeared originally [2]. 1361 | // 1362 | // The *functions.js* file contains the built-in functions. 1363 | // 1364 | // The reason why we evaluate the arguments, is in the case where 1365 | // we try to pass a variable to a function, like: `saturate(@color)`. 1366 | // The function should receive the value, not the variable. 1367 | // 1368 | eval: function (env) { 1369 | var args = this.args.map(function (a) { return a.eval(env) }); 1370 | 1371 | if (this.name in tree.functions) { // 1. 1372 | return tree.functions[this.name].apply(tree.functions, args); 1373 | } else { // 2. 1374 | return new(tree.Anonymous)(this.name + 1375 | "(" + args.map(function (a) { return a.toCSS() }).join(', ') + ")"); 1376 | } 1377 | }, 1378 | 1379 | toCSS: function (env) { 1380 | return this.eval(env).toCSS(); 1381 | } 1382 | }; 1383 | 1384 | })(require('less/tree')); 1385 | (function (tree) { 1386 | // 1387 | // RGB Colors - #ff0014, #eee 1388 | // 1389 | tree.Color = function (rgb, a) { 1390 | // 1391 | // The end goal here, is to parse the arguments 1392 | // into an integer triplet, such as `128, 255, 0` 1393 | // 1394 | // This facilitates operations and conversions. 1395 | // 1396 | if (Array.isArray(rgb)) { 1397 | this.rgb = rgb; 1398 | } else if (rgb.length == 6) { 1399 | this.rgb = rgb.match(/.{2}/g).map(function (c) { 1400 | return parseInt(c, 16); 1401 | }); 1402 | } else { 1403 | this.rgb = rgb.split('').map(function (c) { 1404 | return parseInt(c + c, 16); 1405 | }); 1406 | } 1407 | this.alpha = typeof(a) === 'number' ? a : 1; 1408 | }; 1409 | tree.Color.prototype = { 1410 | eval: function () { return this }, 1411 | 1412 | // 1413 | // If we have some transparency, the only way to represent it 1414 | // is via `rgba`. Otherwise, we use the hex representation, 1415 | // which has better compatibility with older browsers. 1416 | // Values are capped between `0` and `255`, rounded and zero-padded. 1417 | // 1418 | toCSS: function () { 1419 | if (this.alpha < 1.0) { 1420 | return "rgba(" + this.rgb.map(function (c) { 1421 | return Math.round(c); 1422 | }).concat(this.alpha).join(', ') + ")"; 1423 | } else { 1424 | return '#' + this.rgb.map(function (i) { 1425 | i = Math.round(i); 1426 | i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); 1427 | return i.length === 1 ? '0' + i : i; 1428 | }).join(''); 1429 | } 1430 | }, 1431 | 1432 | // 1433 | // Operations have to be done per-channel, if not, 1434 | // channels will spill onto each other. Once we have 1435 | // our result, in the form of an integer triplet, 1436 | // we create a new Color node to hold the result. 1437 | // 1438 | operate: function (op, other) { 1439 | var result = []; 1440 | 1441 | if (! (other instanceof tree.Color)) { 1442 | other = other.toColor(); 1443 | } 1444 | 1445 | for (var c = 0; c < 3; c++) { 1446 | result[c] = tree.operate(op, this.rgb[c], other.rgb[c]); 1447 | } 1448 | return new(tree.Color)(result); 1449 | }, 1450 | 1451 | toHSL: function () { 1452 | var r = this.rgb[0] / 255, 1453 | g = this.rgb[1] / 255, 1454 | b = this.rgb[2] / 255, 1455 | a = this.alpha; 1456 | 1457 | var max = Math.max(r, g, b), min = Math.min(r, g, b); 1458 | var h, s, l = (max + min) / 2, d = max - min; 1459 | 1460 | if (max === min) { 1461 | h = s = 0; 1462 | } else { 1463 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 1464 | 1465 | switch (max) { 1466 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 1467 | case g: h = (b - r) / d + 2; break; 1468 | case b: h = (r - g) / d + 4; break; 1469 | } 1470 | h /= 6; 1471 | } 1472 | return { h: h * 360, s: s, l: l, a: a }; 1473 | } 1474 | }; 1475 | 1476 | 1477 | })(require('less/tree')); 1478 | (function (tree) { 1479 | 1480 | tree.Comment = function (value, silent) { 1481 | this.value = value; 1482 | this.silent = !!silent; 1483 | }; 1484 | tree.Comment.prototype = { 1485 | toCSS: function (env) { 1486 | return env.compress ? '' : this.value; 1487 | }, 1488 | eval: function () { return this } 1489 | }; 1490 | 1491 | })(require('less/tree')); 1492 | (function (tree) { 1493 | 1494 | // 1495 | // A number with a unit 1496 | // 1497 | tree.Dimension = function (value, unit) { 1498 | this.value = parseFloat(value); 1499 | this.unit = unit || null; 1500 | }; 1501 | 1502 | tree.Dimension.prototype = { 1503 | eval: function () { return this }, 1504 | toColor: function () { 1505 | return new(tree.Color)([this.value, this.value, this.value]); 1506 | }, 1507 | toCSS: function () { 1508 | var css = this.value + this.unit; 1509 | return css; 1510 | }, 1511 | 1512 | // In an operation between two Dimensions, 1513 | // we default to the first Dimension's unit, 1514 | // so `1px + 2em` will yield `3px`. 1515 | // In the future, we could implement some unit 1516 | // conversions such that `100cm + 10mm` would yield 1517 | // `101cm`. 1518 | operate: function (op, other) { 1519 | return new(tree.Dimension) 1520 | (tree.operate(op, this.value, other.value), 1521 | this.unit || other.unit); 1522 | } 1523 | }; 1524 | 1525 | })(require('less/tree')); 1526 | (function (tree) { 1527 | 1528 | tree.Directive = function (name, value) { 1529 | this.name = name; 1530 | if (Array.isArray(value)) { 1531 | this.ruleset = new(tree.Ruleset)([], value); 1532 | } else { 1533 | this.value = value; 1534 | } 1535 | }; 1536 | tree.Directive.prototype = { 1537 | toCSS: function (ctx, env) { 1538 | if (this.ruleset) { 1539 | this.ruleset.root = true; 1540 | return this.name + (env.compress ? '{' : ' {\n ') + 1541 | this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + 1542 | (env.compress ? '}': '\n}\n'); 1543 | } else { 1544 | return this.name + ' ' + this.value.toCSS() + ';\n'; 1545 | } 1546 | }, 1547 | eval: function (env) { 1548 | env.frames.unshift(this); 1549 | this.ruleset = this.ruleset && this.ruleset.eval(env); 1550 | env.frames.shift(); 1551 | return this; 1552 | }, 1553 | variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, 1554 | find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, 1555 | rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) } 1556 | }; 1557 | 1558 | })(require('less/tree')); 1559 | (function (tree) { 1560 | 1561 | tree.Element = function (combinator, value) { 1562 | this.combinator = combinator instanceof tree.Combinator ? 1563 | combinator : new(tree.Combinator)(combinator); 1564 | this.value = value.trim(); 1565 | }; 1566 | tree.Element.prototype.toCSS = function (env) { 1567 | return this.combinator.toCSS(env || {}) + this.value; 1568 | }; 1569 | 1570 | tree.Combinator = function (value) { 1571 | if (value === ' ') { 1572 | this.value = ' '; 1573 | } else { 1574 | this.value = value ? value.trim() : ""; 1575 | } 1576 | }; 1577 | tree.Combinator.prototype.toCSS = function (env) { 1578 | return { 1579 | '' : '', 1580 | ' ' : ' ', 1581 | '&' : '', 1582 | ':' : ' :', 1583 | '::': '::', 1584 | '+' : env.compress ? '+' : ' + ', 1585 | '~' : env.compress ? '~' : ' ~ ', 1586 | '>' : env.compress ? '>' : ' > ' 1587 | }[this.value]; 1588 | }; 1589 | 1590 | })(require('less/tree')); 1591 | (function (tree) { 1592 | 1593 | tree.Expression = function (value) { this.value = value }; 1594 | tree.Expression.prototype = { 1595 | eval: function (env) { 1596 | if (this.value.length > 1) { 1597 | return new(tree.Expression)(this.value.map(function (e) { 1598 | return e.eval(env); 1599 | })); 1600 | } else { 1601 | return this.value[0].eval(env); 1602 | } 1603 | }, 1604 | toCSS: function (env) { 1605 | return this.value.map(function (e) { 1606 | return e.toCSS(env); 1607 | }).join(' '); 1608 | } 1609 | }; 1610 | 1611 | })(require('less/tree')); 1612 | (function (tree) { 1613 | // 1614 | // CSS @import node 1615 | // 1616 | // The general strategy here is that we don't want to wait 1617 | // for the parsing to be completed, before we start importing 1618 | // the file. That's because in the context of a browser, 1619 | // most of the time will be spent waiting for the server to respond. 1620 | // 1621 | // On creation, we push the import path to our import queue, though 1622 | // `import,push`, we also pass it a callback, which it'll call once 1623 | // the file has been fetched, and parsed. 1624 | // 1625 | tree.Import = function (path, imports) { 1626 | var that = this; 1627 | 1628 | this._path = path; 1629 | 1630 | // The '.less' extension is optional 1631 | if (path instanceof tree.Quoted) { 1632 | this.path = /\.(le?|c)ss$/.test(path.content) ? path.content : path.content + '.less'; 1633 | } else { 1634 | this.path = path.value.content || path.value; 1635 | } 1636 | 1637 | this.css = /css$/.test(this.path); 1638 | 1639 | // Only pre-compile .less files 1640 | if (! this.css) { 1641 | imports.push(this.path, function (root) { 1642 | if (! root) { 1643 | throw new(Error)("Error parsing " + that.path); 1644 | } 1645 | that.root = root; 1646 | }); 1647 | } 1648 | }; 1649 | 1650 | // 1651 | // The actual import node doesn't return anything, when converted to CSS. 1652 | // The reason is that it's used at the evaluation stage, so that the rules 1653 | // it imports can be treated like any other rules. 1654 | // 1655 | // In `eval`, we make sure all Import nodes get evaluated, recursively, so 1656 | // we end up with a flat structure, which can easily be imported in the parent 1657 | // ruleset. 1658 | // 1659 | tree.Import.prototype = { 1660 | toCSS: function () { 1661 | if (this.css) { 1662 | return "@import " + this._path.toCSS() + ';\n'; 1663 | } else { 1664 | return ""; 1665 | } 1666 | }, 1667 | eval: function (env) { 1668 | var ruleset; 1669 | 1670 | if (this.css) { 1671 | return this; 1672 | } else { 1673 | ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0)); 1674 | 1675 | for (var i = 0; i < ruleset.rules.length; i++) { 1676 | if (ruleset.rules[i] instanceof tree.Import) { 1677 | Array.prototype 1678 | .splice 1679 | .apply(ruleset.rules, 1680 | [i, 1].concat(ruleset.rules[i].eval(env))); 1681 | } 1682 | } 1683 | return ruleset.rules; 1684 | } 1685 | } 1686 | }; 1687 | 1688 | })(require('less/tree')); 1689 | (function (tree) { 1690 | 1691 | tree.JavaScript = function (string, index) { 1692 | this.expression = string; 1693 | this.index = index; 1694 | }; 1695 | tree.JavaScript.prototype = { 1696 | toCSS: function () { 1697 | return JSON.stringify(this.evaluated); 1698 | }, 1699 | eval: function (env) { 1700 | var result, 1701 | expression = new(Function)('return (' + this.expression + ')'), 1702 | context = {}; 1703 | 1704 | for (var k in env.frames[0].variables()) { 1705 | context[k.slice(1)] = { 1706 | value: env.frames[0].variables()[k].value, 1707 | toJS: function () { 1708 | return this.value.eval(env).toCSS(); 1709 | } 1710 | }; 1711 | } 1712 | 1713 | try { 1714 | this.evaluated = expression.call(context); 1715 | } catch (e) { 1716 | throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , 1717 | index: this.index }; 1718 | } 1719 | return this; 1720 | } 1721 | }; 1722 | 1723 | })(require('less/tree')); 1724 | 1725 | (function (tree) { 1726 | 1727 | tree.Keyword = function (value) { this.value = value }; 1728 | tree.Keyword.prototype = { 1729 | eval: function () { return this }, 1730 | toCSS: function () { return this.value } 1731 | }; 1732 | 1733 | })(require('less/tree')); 1734 | (function (tree) { 1735 | 1736 | tree.mixin = {}; 1737 | tree.mixin.Call = function (elements, args, index) { 1738 | this.selector = new(tree.Selector)(elements); 1739 | this.arguments = args; 1740 | this.index = index; 1741 | }; 1742 | tree.mixin.Call.prototype = { 1743 | eval: function (env) { 1744 | var mixins, rules = [], match = false; 1745 | 1746 | for (var i = 0; i < env.frames.length; i++) { 1747 | if ((mixins = env.frames[i].find(this.selector)).length > 0) { 1748 | for (var m = 0; m < mixins.length; m++) { 1749 | if (mixins[m].match(this.arguments, env)) { 1750 | try { 1751 | Array.prototype.push.apply( 1752 | rules, mixins[m].eval(env, this.arguments).rules); 1753 | match = true; 1754 | } catch (e) { 1755 | throw { message: e.message, index: e.index, stack: e.stack, call: this.index }; 1756 | } 1757 | } 1758 | } 1759 | if (match) { 1760 | return rules; 1761 | } else { 1762 | throw { message: 'No matching definition was found for `' + 1763 | this.selector.toCSS().trim() + '(' + 1764 | this.arguments.map(function (a) { 1765 | return a.toCSS(); 1766 | }).join(', ') + ")`", 1767 | index: this.index }; 1768 | } 1769 | } 1770 | } 1771 | throw { message: this.selector.toCSS().trim() + " is undefined", 1772 | index: this.index }; 1773 | } 1774 | }; 1775 | 1776 | tree.mixin.Definition = function (name, params, rules) { 1777 | this.name = name; 1778 | this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])]; 1779 | this.params = params; 1780 | this.arity = params.length; 1781 | this.rules = rules; 1782 | this._lookups = {}; 1783 | this.required = params.reduce(function (count, p) { 1784 | if (p.name && !p.value) { return count + 1 } 1785 | else { return count } 1786 | }, 0); 1787 | this.parent = tree.Ruleset.prototype; 1788 | this.frames = []; 1789 | }; 1790 | tree.mixin.Definition.prototype = { 1791 | toCSS: function () { return "" }, 1792 | variable: function (name) { return this.parent.variable.call(this, name) }, 1793 | variables: function () { return this.parent.variables.call(this) }, 1794 | find: function () { return this.parent.find.apply(this, arguments) }, 1795 | rulesets: function () { return this.parent.rulesets.apply(this) }, 1796 | 1797 | eval: function (env, args) { 1798 | var frame = new(tree.Ruleset)(null, []), context; 1799 | 1800 | for (var i = 0, val; i < this.params.length; i++) { 1801 | if (this.params[i].name) { 1802 | if (val = (args && args[i]) || this.params[i].value) { 1803 | frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env))); 1804 | } else { 1805 | throw { message: "wrong number of arguments for " + this.name + 1806 | ' (' + args.length + ' for ' + this.arity + ')' }; 1807 | } 1808 | } 1809 | } 1810 | return new(tree.Ruleset)(null, this.rules.slice(0)).eval({ 1811 | frames: [this, frame].concat(this.frames, env.frames) 1812 | }); 1813 | }, 1814 | match: function (args, env) { 1815 | var argsLength = (args && args.length) || 0, len; 1816 | 1817 | if (argsLength < this.required) { return false } 1818 | 1819 | len = Math.min(argsLength, this.arity); 1820 | 1821 | for (var i = 0; i < len; i++) { 1822 | if (!this.params[i].name) { 1823 | if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { 1824 | return false; 1825 | } 1826 | } 1827 | } 1828 | return true; 1829 | } 1830 | }; 1831 | 1832 | })(require('less/tree')); 1833 | (function (tree) { 1834 | 1835 | tree.Operation = function (op, operands) { 1836 | this.op = op.trim(); 1837 | this.operands = operands; 1838 | }; 1839 | tree.Operation.prototype.eval = function (env) { 1840 | var a = this.operands[0].eval(env), 1841 | b = this.operands[1].eval(env), 1842 | temp; 1843 | 1844 | if (a instanceof tree.Dimension && b instanceof tree.Color) { 1845 | if (this.op === '*' || this.op === '+') { 1846 | temp = b, b = a, a = temp; 1847 | } else { 1848 | throw { name: "OperationError", 1849 | message: "Can't substract or divide a color from a number" }; 1850 | } 1851 | } 1852 | return a.operate(this.op, b); 1853 | }; 1854 | 1855 | tree.operate = function (op, a, b) { 1856 | switch (op) { 1857 | case '+': return a + b; 1858 | case '-': return a - b; 1859 | case '*': return a * b; 1860 | case '/': return a / b; 1861 | } 1862 | }; 1863 | 1864 | })(require('less/tree')); 1865 | (function (tree) { 1866 | 1867 | tree.Quoted = function (value, content) { 1868 | this.value = value; 1869 | this.content = content; 1870 | }; 1871 | tree.Quoted.prototype = { 1872 | toCSS: function () { 1873 | return this.value; 1874 | }, 1875 | eval: function () { 1876 | return this; 1877 | } 1878 | }; 1879 | 1880 | })(require('less/tree')); 1881 | (function (tree) { 1882 | 1883 | tree.Rule = function (name, value, important, index) { 1884 | this.name = name; 1885 | this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); 1886 | this.important = important ? ' ' + important.trim() : ''; 1887 | this.index = index; 1888 | 1889 | if (name.charAt(0) === '@') { 1890 | this.variable = true; 1891 | } else { this.variable = false } 1892 | }; 1893 | tree.Rule.prototype.toCSS = function (env) { 1894 | if (this.variable) { return "" } 1895 | else { 1896 | return this.name + (env.compress ? ':' : ': ') + 1897 | this.value.toCSS(env) + 1898 | this.important + ";"; 1899 | } 1900 | }; 1901 | 1902 | tree.Rule.prototype.eval = function (context) { 1903 | return new(tree.Rule)(this.name, this.value.eval(context), this.important, this.index); 1904 | }; 1905 | 1906 | tree.Shorthand = function (a, b) { 1907 | this.a = a; 1908 | this.b = b; 1909 | }; 1910 | 1911 | tree.Shorthand.prototype = { 1912 | toCSS: function (env) { 1913 | return this.a.toCSS(env) + "/" + this.b.toCSS(env); 1914 | }, 1915 | eval: function () { return this } 1916 | }; 1917 | 1918 | })(require('less/tree')); 1919 | (function (tree) { 1920 | 1921 | tree.Ruleset = function (selectors, rules) { 1922 | this.selectors = selectors; 1923 | this.rules = rules; 1924 | this._lookups = {}; 1925 | }; 1926 | tree.Ruleset.prototype = { 1927 | eval: function (env) { 1928 | var ruleset = new(tree.Ruleset)(this.selectors, this.rules.slice(0)); 1929 | 1930 | ruleset.root = this.root; 1931 | 1932 | // push the current ruleset to the frames stack 1933 | env.frames.unshift(ruleset); 1934 | 1935 | // Evaluate imports 1936 | if (ruleset.root) { 1937 | for (var i = 0; i < ruleset.rules.length; i++) { 1938 | if (ruleset.rules[i] instanceof tree.Import) { 1939 | Array.prototype.splice 1940 | .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); 1941 | } 1942 | } 1943 | } 1944 | 1945 | // Store the frames around mixin definitions, 1946 | // so they can be evaluated like closures when the time comes. 1947 | for (var i = 0; i < ruleset.rules.length; i++) { 1948 | if (ruleset.rules[i] instanceof tree.mixin.Definition) { 1949 | ruleset.rules[i].frames = env.frames.slice(0); 1950 | } 1951 | } 1952 | 1953 | // Evaluate mixin calls. 1954 | for (var i = 0; i < ruleset.rules.length; i++) { 1955 | if (ruleset.rules[i] instanceof tree.mixin.Call) { 1956 | Array.prototype.splice 1957 | .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); 1958 | } 1959 | } 1960 | 1961 | // Evaluate everything else 1962 | for (var i = 0, rule; i < ruleset.rules.length; i++) { 1963 | rule = ruleset.rules[i]; 1964 | 1965 | if (! (rule instanceof tree.mixin.Definition)) { 1966 | ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; 1967 | } 1968 | } 1969 | 1970 | // Pop the stack 1971 | env.frames.shift(); 1972 | 1973 | return ruleset; 1974 | }, 1975 | match: function (args) { 1976 | return !args || args.length === 0; 1977 | }, 1978 | variables: function () { 1979 | if (this._variables) { return this._variables } 1980 | else { 1981 | return this._variables = this.rules.reduce(function (hash, r) { 1982 | if (r instanceof tree.Rule && r.variable === true) { 1983 | hash[r.name] = r; 1984 | } 1985 | return hash; 1986 | }, {}); 1987 | } 1988 | }, 1989 | variable: function (name) { 1990 | return this.variables()[name]; 1991 | }, 1992 | rulesets: function () { 1993 | if (this._rulesets) { return this._rulesets } 1994 | else { 1995 | return this._rulesets = this.rules.filter(function (r) { 1996 | return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); 1997 | }); 1998 | } 1999 | }, 2000 | find: function (selector, self) { 2001 | self = self || this; 2002 | var rules = [], rule, match, 2003 | key = selector.toCSS(); 2004 | 2005 | if (key in this._lookups) { return this._lookups[key] } 2006 | 2007 | this.rulesets().forEach(function (rule) { 2008 | if (rule !== self) { 2009 | for (var j = 0; j < rule.selectors.length; j++) { 2010 | if (match = selector.match(rule.selectors[j])) { 2011 | if (selector.elements.length > 1) { 2012 | Array.prototype.push.apply(rules, rule.find( 2013 | new(tree.Selector)(selector.elements.slice(1)), self)); 2014 | } else { 2015 | rules.push(rule); 2016 | } 2017 | break; 2018 | } 2019 | } 2020 | } 2021 | }); 2022 | return this._lookups[key] = rules; 2023 | }, 2024 | // 2025 | // Entry point for code generation 2026 | // 2027 | // `context` holds an array of arrays. 2028 | // 2029 | toCSS: function (context, env) { 2030 | var css = [], // The CSS output 2031 | rules = [], // node.Rule instances 2032 | rulesets = [], // node.Ruleset instances 2033 | paths = [], // Current selectors 2034 | selector, // The fully rendered selector 2035 | rule; 2036 | 2037 | if (! this.root) { 2038 | if (context.length === 0) { 2039 | paths = this.selectors.map(function (s) { return [s] }); 2040 | } else { 2041 | for (var s = 0; s < this.selectors.length; s++) { 2042 | for (var c = 0; c < context.length; c++) { 2043 | paths.push(context[c].concat([this.selectors[s]])); 2044 | } 2045 | } 2046 | } 2047 | } 2048 | 2049 | // Compile rules and rulesets 2050 | for (var i = 0; i < this.rules.length; i++) { 2051 | rule = this.rules[i]; 2052 | 2053 | if (rule.rules || (rule instanceof tree.Directive)) { 2054 | rulesets.push(rule.toCSS(paths, env)); 2055 | } else if (rule instanceof tree.Comment) { 2056 | if (!rule.silent) { 2057 | if (this.root) { 2058 | rulesets.push(rule.toCSS(env)); 2059 | } else { 2060 | rules.push(rule.toCSS(env)); 2061 | } 2062 | } 2063 | } else { 2064 | if (rule.toCSS && !rule.variable) { 2065 | rules.push(rule.toCSS(env)); 2066 | } else if (rule.value && !rule.variable) { 2067 | rules.push(rule.value.toString()); 2068 | } 2069 | } 2070 | } 2071 | 2072 | rulesets = rulesets.join(''); 2073 | 2074 | // If this is the root node, we don't render 2075 | // a selector, or {}. 2076 | // Otherwise, only output if this ruleset has rules. 2077 | if (this.root) { 2078 | css.push(rules.join(env.compress ? '' : '\n')); 2079 | } else { 2080 | if (rules.length > 0) { 2081 | selector = paths.map(function (p) { 2082 | return p.map(function (s) { 2083 | return s.toCSS(env); 2084 | }).join('').trim(); 2085 | }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', ')); 2086 | css.push(selector, 2087 | (env.compress ? '{' : ' {\n ') + 2088 | rules.join(env.compress ? '' : '\n ') + 2089 | (env.compress ? '}' : '\n}\n')); 2090 | } 2091 | } 2092 | css.push(rulesets); 2093 | 2094 | return css.join('') + (env.compress ? '\n' : ''); 2095 | } 2096 | }; 2097 | })(require('less/tree')); 2098 | (function (tree) { 2099 | 2100 | tree.Selector = function (elements) { 2101 | this.elements = elements; 2102 | if (this.elements[0].combinator.value === "") { 2103 | this.elements[0].combinator.value = ' '; 2104 | } 2105 | }; 2106 | tree.Selector.prototype.match = function (other) { 2107 | if (this.elements[0].value === other.elements[0].value) { 2108 | return true; 2109 | } else { 2110 | return false; 2111 | } 2112 | }; 2113 | tree.Selector.prototype.toCSS = function (env) { 2114 | if (this._css) { return this._css } 2115 | 2116 | return this._css = this.elements.map(function (e) { 2117 | if (typeof(e) === 'string') { 2118 | return ' ' + e.trim(); 2119 | } else { 2120 | return e.toCSS(env); 2121 | } 2122 | }).join(''); 2123 | }; 2124 | 2125 | })(require('less/tree')); 2126 | (function (tree) { 2127 | 2128 | tree.URL = function (val) { 2129 | this.value = val; 2130 | }; 2131 | tree.URL.prototype = { 2132 | toCSS: function () { 2133 | return "url(" + this.value.toCSS() + ")"; 2134 | }, 2135 | eval: function (ctx) { 2136 | this.value = this.value.eval(ctx); 2137 | return this; 2138 | } 2139 | }; 2140 | 2141 | })(require('less/tree')); 2142 | (function (tree) { 2143 | 2144 | tree.Value = function (value) { 2145 | this.value = value; 2146 | this.is = 'value'; 2147 | }; 2148 | tree.Value.prototype = { 2149 | eval: function (env) { 2150 | if (this.value.length === 1) { 2151 | return this.value[0].eval(env); 2152 | } else { 2153 | return new(tree.Value)(this.value.map(function (v) { 2154 | return v.eval(env); 2155 | })); 2156 | } 2157 | }, 2158 | toCSS: function (env) { 2159 | return this.value.map(function (e) { 2160 | return e.toCSS(env); 2161 | }).join(env.compress ? ',' : ', '); 2162 | } 2163 | }; 2164 | 2165 | })(require('less/tree')); 2166 | (function (tree) { 2167 | 2168 | tree.Variable = function (name, index) { this.name = name, this.index = index }; 2169 | tree.Variable.prototype = { 2170 | eval: function (env) { 2171 | var variable, v, name = this.name; 2172 | 2173 | if (variable = tree.find(env.frames, function (frame) { 2174 | if (v = frame.variable(name)) { 2175 | return v.value.eval(env); 2176 | } 2177 | })) { return variable } 2178 | else { 2179 | throw { message: "variable " + this.name + " is undefined", 2180 | index: this.index }; 2181 | } 2182 | } 2183 | }; 2184 | 2185 | })(require('less/tree')); 2186 | require('less/tree').find = function (obj, fun) { 2187 | for (var i = 0, r; i < obj.length; i++) { 2188 | if (r = fun.call(obj, obj[i])) { return r } 2189 | } 2190 | return null; 2191 | }; 2192 | // 2193 | // browser.js - client-side engine 2194 | // 2195 | 2196 | var isFileProtocol = (location.protocol === 'file:' || 2197 | location.protocol === 'chrome:' || 2198 | location.protocol === 'resource:'); 2199 | 2200 | less.env = location.hostname == '127.0.0.1' || 2201 | location.hostname == '0.0.0.0' || 2202 | location.hostname == 'localhost' || 2203 | location.port.length > 0 || 2204 | isFileProtocol ? 'development' 2205 | : 'production'; 2206 | 2207 | // Load styles asynchronously (default: false) 2208 | // 2209 | // This is set to `false` by default, so that the body 2210 | // doesn't start loading before the stylesheets are parsed. 2211 | // Setting this to `true` can result in flickering. 2212 | // 2213 | less.async = false; 2214 | 2215 | // Interval between watch polls 2216 | less.poll = isFileProtocol ? 1000 : 1500; 2217 | 2218 | // 2219 | // Watch mode 2220 | // 2221 | less.watch = function () { return this.watchMode = true }; 2222 | less.unwatch = function () { return this.watchMode = false }; 2223 | 2224 | if (less.env === 'development') { 2225 | less.optimization = 0; 2226 | 2227 | if (/!watch/.test(location.hash)) { 2228 | less.watch(); 2229 | } 2230 | less.watchTimer = setInterval(function () { 2231 | if (less.watchMode) { 2232 | loadStyleSheets(function (root, sheet, env) { 2233 | if (root) { 2234 | createCSS(root.toCSS(), sheet, env.lastModified); 2235 | } 2236 | }); 2237 | } 2238 | }, less.poll); 2239 | } else { 2240 | less.optimization = 3; 2241 | } 2242 | 2243 | var cache; 2244 | 2245 | try { 2246 | cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; 2247 | } catch (_) { 2248 | cache = null; 2249 | } 2250 | 2251 | // 2252 | // Get all tags with the 'rel' attribute set to "stylesheet/less" 2253 | // 2254 | var links = document.getElementsByTagName('link'); 2255 | var typePattern = /^text\/(x-)?less$/; 2256 | 2257 | less.sheets = []; 2258 | 2259 | for (var i = 0; i < links.length; i++) { 2260 | if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && 2261 | (links[i].type.match(typePattern)))) { 2262 | less.sheets.push(links[i]); 2263 | } 2264 | } 2265 | 2266 | 2267 | less.refresh = function (reload) { 2268 | var startTime = endTime = new(Date); 2269 | 2270 | loadStyleSheets(function (root, sheet, env) { 2271 | if (env.local) { 2272 | log("loading " + sheet.href + " from cache."); 2273 | } else { 2274 | log("parsed " + sheet.href + " successfully."); 2275 | createCSS(root.toCSS(), sheet, env.lastModified); 2276 | } 2277 | log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms'); 2278 | (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms'); 2279 | endTime = new(Date); 2280 | }, reload); 2281 | 2282 | loadStyles(); 2283 | }; 2284 | less.refreshStyles = loadStyles; 2285 | 2286 | less.refresh(less.env === 'development'); 2287 | 2288 | function loadStyles() { 2289 | var styles = document.getElementsByTagName('style'); 2290 | for (var i = 0; i < styles.length; i++) { 2291 | if (styles[i].type.match(typePattern)) { 2292 | new(less.Parser)().parse(styles[i].innerHTML || '', function (e, tree) { 2293 | styles[i].type = 'text/css'; 2294 | styles[i].innerHTML = tree.toCSS(); 2295 | }); 2296 | } 2297 | } 2298 | } 2299 | 2300 | function loadStyleSheets(callback, reload) { 2301 | for (var i = 0; i < less.sheets.length; i++) { 2302 | loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1)); 2303 | } 2304 | } 2305 | 2306 | function loadStyleSheet(sheet, callback, reload, remaining) { 2307 | var href = sheet.href.replace(/\?.*$/, ''); 2308 | var css = cache && cache.getItem(href); 2309 | var timestamp = cache && cache.getItem(href + ':timestamp'); 2310 | var styles = { css: css, timestamp: timestamp }; 2311 | 2312 | xhr(sheet.href, function (data, lastModified) { 2313 | if (!reload && styles && 2314 | (new(Date)(lastModified).valueOf() === 2315 | new(Date)(styles.timestamp).valueOf())) { 2316 | // Use local copy 2317 | createCSS(styles.css, sheet); 2318 | callback(null, sheet, { local: true, remaining: remaining }); 2319 | } else { 2320 | // Use remote copy (re-parse) 2321 | new(less.Parser)({ 2322 | optimization: less.optimization, 2323 | paths: [href.replace(/[\w\.-]+$/, '')] 2324 | }).parse(data, function (e, root) { 2325 | if (e) { return error(e, href) } 2326 | try { 2327 | callback(root, sheet, { local: false, lastModified: lastModified, remaining: remaining }); 2328 | removeNode(document.getElementById('less-error-message:' + extractId(href))); 2329 | } catch (e) { 2330 | error(e, href); 2331 | } 2332 | }); 2333 | } 2334 | }, function (status, url) { 2335 | throw new(Error)("Couldn't load " + url+ " (" + status + ")"); 2336 | }); 2337 | } 2338 | 2339 | function extractId(href) { 2340 | return href.replace(/^[a-z]+:\/\/?[^\/]+/, '') // Remove protocol & domain 2341 | .replace(/^\//, '') // Remove root / 2342 | .replace(/\?.*$/, '') // Remove query 2343 | .replace(/\.[^\/]+$/, '') // Remove file extension 2344 | .replace(/[^\w-]+/g, '-'); // Replace illegal characters 2345 | } 2346 | 2347 | function createCSS(styles, sheet, lastModified) { 2348 | var css; 2349 | 2350 | // Strip the query-string 2351 | var href = sheet.href ? sheet.href.replace(/\?.*$/, '') : ''; 2352 | 2353 | // If there is no title set, use the filename, minus the extension 2354 | var id = 'less:' + (sheet.title || extractId(href)); 2355 | 2356 | // If the stylesheet doesn't exist, create a new node 2357 | if ((css = document.getElementById(id)) === null) { 2358 | css = document.createElement('style'); 2359 | css.type = 'text/css'; 2360 | css.media = sheet.media; 2361 | css.id = id; 2362 | document.getElementsByTagName('head')[0].appendChild(css); 2363 | } 2364 | 2365 | if (css.styleSheet) { // IE 2366 | try { 2367 | css.styleSheet.cssText = styles; 2368 | } catch (e) { 2369 | throw new(Error)("Couldn't reassign styleSheet.cssText."); 2370 | } 2371 | } else { 2372 | (function (node) { 2373 | if (css.childNodes.length > 0) { 2374 | if (css.firstChild.nodeValue !== node.nodeValue) { 2375 | css.replaceChild(node, css.firstChild); 2376 | } 2377 | } else { 2378 | css.appendChild(node); 2379 | } 2380 | })(document.createTextNode(styles)); 2381 | } 2382 | 2383 | // Don't update the local store if the file wasn't modified 2384 | if (lastModified && cache) { 2385 | log('saving ' + href + ' to cache.'); 2386 | cache.setItem(href, styles); 2387 | cache.setItem(href + ':timestamp', lastModified); 2388 | } 2389 | } 2390 | 2391 | function xhr(url, callback, errback) { 2392 | var xhr = getXMLHttpRequest(); 2393 | var async = isFileProtocol ? false : less.async; 2394 | 2395 | if (typeof(xhr.overrideMimeType) === 'function') { 2396 | xhr.overrideMimeType('text/css'); 2397 | } 2398 | xhr.open('GET', url, async); 2399 | xhr.send(null); 2400 | 2401 | if (isFileProtocol) { 2402 | if (xhr.status === 0) { 2403 | callback(xhr.responseText); 2404 | } else { 2405 | errback(xhr.status); 2406 | } 2407 | } else if (async) { 2408 | xhr.onreadystatechange = function () { 2409 | if (xhr.readyState == 4) { 2410 | handleResponse(xhr, callback, errback); 2411 | } 2412 | }; 2413 | } else { 2414 | handleResponse(xhr, callback, errback); 2415 | } 2416 | 2417 | function handleResponse(xhr, callback, errback) { 2418 | if (xhr.status >= 200 && xhr.status < 300) { 2419 | callback(xhr.responseText, 2420 | xhr.getResponseHeader("Last-Modified")); 2421 | } else if (typeof(errback) === 'function') { 2422 | errback(xhr.status, url); 2423 | } 2424 | } 2425 | } 2426 | 2427 | function getXMLHttpRequest() { 2428 | if (window.XMLHttpRequest) { 2429 | return new(XMLHttpRequest); 2430 | } else { 2431 | try { 2432 | return new(ActiveXObject)("MSXML2.XMLHTTP.3.0"); 2433 | } catch (e) { 2434 | log("browser doesn't support AJAX."); 2435 | return null; 2436 | } 2437 | } 2438 | } 2439 | 2440 | function removeNode(node) { 2441 | return node && node.parentNode.removeChild(node); 2442 | } 2443 | 2444 | function log(str) { 2445 | if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) } 2446 | } 2447 | 2448 | function error(e, href) { 2449 | var id = 'less-error-message:' + extractId(href); 2450 | 2451 | if (! e.extract) { throw e } 2452 | 2453 | var template = ['
', 2454 | '
[-1]{0}
', 2455 | '
[0]{current}
', 2456 | '
[1]{2}
', 2457 | '
'].join('\n'); 2458 | 2459 | var elem = document.createElement('div'), timer; 2460 | elem.id = id; 2461 | elem.className = "less-error-message"; 2462 | elem.innerHTML = '

' + (e.message || 'There is an error in your .less file') + '

' + 2463 | '

' + href + " " + 2464 | 'on line ' + e.line + ', column ' + (e.column + 1) + ':

' + 2465 | template.replace(/\[(-?\d)\]/g, function (_, i) { 2466 | return (parseInt(e.line) + parseInt(i)) || ''; 2467 | }).replace(/\{(\d)\}/g, function (_, i) { 2468 | return e.extract[parseInt(i)] || ''; 2469 | }).replace(/\{current\}/, e.extract[1].slice(0, e.column) + 2470 | '' + 2471 | e.extract[1].slice(e.column) + 2472 | ''); 2473 | // CSS for error messages 2474 | createCSS([ 2475 | '.less-error-message span {', 2476 | 'margin-right: 15px;', 2477 | '}', 2478 | '.less-error-message pre {', 2479 | 'color: #ee4444;', 2480 | 'padding: 4px 0;', 2481 | 'margin: 0;', 2482 | '}', 2483 | '.less-error-message pre.ctx {', 2484 | 'color: #dd7777;', 2485 | '}', 2486 | '.less-error-message h3 {', 2487 | 'padding: 15px 0 5px 0;', 2488 | 'margin: 0;', 2489 | '}', 2490 | '.less-error-message a {', 2491 | 'color: #10a', 2492 | '}', 2493 | '.less-error-message .error {', 2494 | 'color: red;', 2495 | 'font-weight: bold;', 2496 | 'padding-bottom: 2px;', 2497 | 'border-bottom: 1px dashed red;', 2498 | '}' 2499 | ].join('\n'), { title: 'error-message' }); 2500 | 2501 | elem.style.cssText = [ 2502 | "font-family: Arial, sans-serif", 2503 | "border: 1px solid #e00", 2504 | "background-color: #eee", 2505 | "border-radius: 5px", 2506 | "-webkit-border-radius: 5px", 2507 | "-moz-border-radius: 5px", 2508 | "color: #e00", 2509 | "padding: 15px", 2510 | "margin-bottom: 15px" 2511 | ].join(';'); 2512 | 2513 | if (less.env == 'development') { 2514 | timer = setInterval(function () { 2515 | if (document.body) { 2516 | if (document.getElementById(id)) { 2517 | document.body.replaceChild(elem, document.getElementById(id)); 2518 | } else { 2519 | document.body.insertBefore(elem, document.body.firstChild); 2520 | } 2521 | clearInterval(timer); 2522 | } 2523 | }, 10); 2524 | } 2525 | } 2526 | 2527 | })(window); -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | (function(exports){ 2 | // ------------------------------------------------------------------------- 3 | // utils 4 | 5 | function prop(obj, name, getter, setter) { 6 | var m = {}; 7 | if (getter) m.get = getter; 8 | if (setter) m.set = setter; 9 | Object.defineProperty(obj, name, m); 10 | } 11 | 12 | /*function getSelectionState() { 13 | var sel = window.getSelection(); 14 | // the Selection object returned is volatile -- make a copy of its state 15 | // See https://developer.mozilla.org/en/DOM/selection for details 16 | return { 17 | anchorNode: sel.anchorNode, // the node in which the selection begins. 18 | anchorOffset: sel.anchorOffset, // number of characters that the selection's 19 | // anchor is offset within the anchorNode. 20 | focusNode: sel.focusNode, // node in which the selection ends. 21 | focusOffset: sel.focusOffset, // number of characters that the selection's 22 | // focus is offset within the focusNode. 23 | baseNode: sel.baseNode, 24 | baseOffset: sel.baseOffset, 25 | isCollapsed: sel.isCollapsed, // whether the selection's start and end 26 | // points are at the same position. 27 | type: sel.type, 28 | extentNode: sel.extentNode, 29 | extentOffset: sel.extentOffset, 30 | //rangeCount: sel.rangeCount, // number of ranges in the selection. 31 | }; 32 | }*/ 33 | 34 | if (!Array.prototype.unique) 35 | Array.prototype.unique = function () { 36 | var buf = [], value; 37 | for (var i=0,L=this.length;i|<\/div>/mg, m; 74 | //var buf = this.textNode.data; 75 | var buf = $.trim(this.content); // innerHTML 76 | while ((m = re.exec(buf)) != null) { count++; } 77 | return count; 78 | }, 79 | 80 | select: function(selection) { 81 | this.node.focus(); 82 | var sel = window.getSelection(); 83 | if (selection) { 84 | if (typeof selection === 'object') { 85 | var range = sel.getRangeAt(0); 86 | range.setStart(selection.anchorNode, selection.anchorOffset); 87 | range.setEnd(selection.anchorNode, selection.anchorOffset); 88 | } 89 | } else { 90 | sel.collapseToStart(); 91 | } 92 | }, 93 | 94 | update: function(noselect) { 95 | //console.log('buffer.update!'); 96 | if (this.mode === 'txt') { 97 | return; 98 | } 99 | noselect = !!noselect || !this.focused; 100 | if (!noselect) { 101 | this.saveSelectionRange(); 102 | this.saveSelection(); 103 | } 104 | if (typeof prettyPrint !== 'undefined') { 105 | prettyPrint(); 106 | } 107 | if (!noselect) { 108 | this.restoreSelection(); 109 | } 110 | }, 111 | 112 | saveSelectionRange: function () { 113 | if (this.focused) { 114 | var sel = window.getSelection(); 115 | //console.log(this.node.isSameNode(sel.anchorNode)); 116 | //console.log('selection ranges:', sel.rangeCount); 117 | if (sel.rangeCount) { 118 | this._range = sel.getRangeAt(0); 119 | } 120 | } 121 | }, 122 | 123 | saveSelection: function() { 124 | if (this._range) { 125 | var startMarker = document.createElement('point-mark'); 126 | startMarker.setAttribute('class', 'start'); 127 | this._range.insertNode(startMarker); 128 | // Insert end cursor marker if any text is selected 129 | if (!this._range.collapsed) { 130 | var endMarker = document.createElement('point-mark'); 131 | endMarker.setAttribute('class', 'end'); 132 | this._range.collapse(); 133 | this._range.insertNode(endMarker); 134 | } 135 | } else { 136 | console.warn('failed to save selection'); 137 | } 138 | }, 139 | 140 | restoreSelection: function() { 141 | if (!this.focused) { 142 | console.warn('tried to restoreSelection of buffer which is not focused'); 143 | return; 144 | } 145 | var sel = window.getSelection(); 146 | var $node = $(this.node); 147 | var startMarker = $node.find('point-mark.start')[0]; 148 | // todo: abort if user is creating a new selection 149 | if (startMarker) { 150 | var endMarker = $node.find('point-mark.end')[0]; 151 | var range = document.createRange(); 152 | if (endMarker) { 153 | range.setStartAfter(startMarker); 154 | range.setEndBefore(endMarker); 155 | } else { 156 | range.selectNode(startMarker); 157 | } 158 | 159 | sel.removeAllRanges(); 160 | sel.addRange(range); 161 | 162 | if (startMarker) startMarker.parentNode.removeChild(startMarker); 163 | if (endMarker) endMarker.parentNode.removeChild(endMarker); 164 | //console.log('restored selection', range) 165 | return true; 166 | } else { 167 | console.log('restoreSelection: no selection state for buffer'); 168 | return false; 169 | } 170 | } 171 | }; 172 | 173 | prop(Buffer.prototype, 'content', function(){ 174 | return this.node.innerHTML; 175 | }, function(str){ 176 | $(this.node).empty().append(document.createTextNode(str)); 177 | this.update(); 178 | this.window.gutter.update(); 179 | }); 180 | 181 | prop(Buffer.prototype, 'html', function(){ 182 | return this.node.innerHTML; 183 | }, function(str){ 184 | this.node.innerHTML = str; 185 | this.update(); 186 | this.window.gutter.update(); 187 | }); 188 | 189 | prop(Buffer.prototype, 'mode', function(){ 190 | var s = this.node.getAttribute('class'); 191 | var m = /(?:^|\s)lang-([\w\d]+)/.exec(s); 192 | return m[1]; 193 | }, function(mode){ 194 | this.node.setAttribute('class', 'prettyprint lang-'+mode); 195 | }); 196 | 197 | 198 | function Window(node) { 199 | this.node = node; 200 | this.buffer = new Buffer(this, $(node).find('pre')[0]); 201 | this.gutter = new Gutter(this, $(node).find('gutter')[0]); 202 | } 203 | Window.prototype = { 204 | update: function() { 205 | this.buffer.update(); 206 | this.gutter.update(); 207 | } 208 | }; 209 | 210 | // ---------------------------------------------------------------------------- 211 | // commands 212 | 213 | exports.commands = { 214 | help: function (rawArgs, subject) { 215 | var self = exports.w.buffer; 216 | //var text = $('#help-buffer')[0].firstChild.data; 217 | //$(self.node).empty()[0].appendChild(document.createTextNode(text)); 218 | //$(self.node).html($('#help-buffer').html()); 219 | self.mode = 'txt'; 220 | self.html = $('#help-buffer').html(); 221 | return ''; 222 | }, 223 | mode: function (rawArgs, setmode) { 224 | var self = exports.w.buffer; 225 | if (setmode) { 226 | self.mode = setmode; 227 | self.update(); 228 | } 229 | return self.mode; 230 | }, 231 | eval: function (rawArgs) { 232 | try { 233 | var r = eval(rawArgs); 234 | console.log('eval>', rawArgs, '-->', r); 235 | try { 236 | r = JSON.stringify(r); 237 | } catch (e) {} 238 | return String(r); 239 | } catch (e) { 240 | return String(e); 241 | } 242 | }, 243 | 244 | // new buffer 245 | 'new-buffer': function() { 246 | var self = exports.w.buffer; 247 | // TODO: window should be able to keep around multiple buffers 248 | // and this command should do e.g. w.buffers.unshift(new Buffer) 249 | self.content = ''; 250 | $(self.node).focus(); 251 | return ''; 252 | }, 253 | // clear current buffer 254 | 'clear-buffer': function () { 255 | var self = exports.w.buffer; 256 | self.content = ''; 257 | }, 258 | 259 | // enable/disable prettify 260 | 'prettify': function (raw, action) { 261 | var withoutInSearch = 262 | document.location.search.indexOf('without-prettify') !== -1; 263 | if (String(action).charAt(0) === 'd') { 264 | if (!withoutInSearch) { 265 | setTimeout(function(){ 266 | document.location.search += 267 | ((document.location.search.indexOf('?') === -1) ? '?' : '&')+ 268 | 'without-prettify'; 269 | },100); 270 | return ''; 271 | } else { 272 | return 'prettify already disabled'; 273 | } 274 | } else { 275 | if (withoutInSearch) { 276 | setTimeout(function(){ 277 | document.location.search = 278 | document.location.search.replace(/without-prettify/g, ''); 279 | },100); 280 | return ''; 281 | } else { 282 | return 'prettify already enabled'; 283 | } 284 | } 285 | }, 286 | 287 | 'load-url': function(raw, url) { 288 | var self = exports.w.buffer; 289 | self.mode = 'txt'; 290 | self.content = 'Loading '+url+'...'; 291 | $.get(url, function(data){ 292 | self.mode = 'html'; // TODO: check content-type header in response 293 | self.content = data; 294 | }); 295 | }, 296 | 297 | // clears the command line history 298 | 'clear-history': function () { 299 | localStorage.removeItem('commandHistory'); 300 | return ''; 301 | } 302 | 303 | }; 304 | 305 | // ------------------------------------------------------------------------- 306 | 307 | // See this on performing "incremental" actions on the selection: 308 | // https://developer.mozilla.org/en/DOM/Selection/modify 309 | 310 | // convenience "on" for event listeners 311 | Node.prototype.on = Node.prototype.addEventListener; 312 | 313 | // BR or not no BR 314 | document.execCommand('insertBrOnReturn', false, false); 315 | 316 | $(function(){ 317 | // currently we only support one window and this is it. 318 | var w = exports.w = new Window($('window')[0]); 319 | 320 | /*['DOMFocusIn', 'DOMFocusOut', 'DOMActivate'].forEach(function(evname){ 321 | document.on(evname, function(ev) { console.log('document', evname, ev); }); 322 | });*/ 323 | 324 | w.buffer.node.on('keydown', function(ev) { 325 | //console.log('buffer.node keydown', ev); 326 | if (ev.keyCode === 9) { 327 | ev.preventDefault(); 328 | var sel = window.getSelection(); 329 | if (sel.isCollapsed) { 330 | if (ev.altKey) { 331 | console.log('TODO: implement point outdent'); 332 | } else { 333 | document.execCommand('insertHTML', false, ' '); 334 | } 335 | } else { 336 | if (ev.altKey) { 337 | console.log('TODO: implement block outdent'); 338 | } else { 339 | console.log('TODO: implement block indent'); 340 | 341 | } 342 | } 343 | } 344 | }) 345 | 346 | if (typeof prettyPrint !== 'undefined') { 347 | // since undo/redo is fucked up by Google Prettify 348 | w.buffer.node.on('keypress', function(ev) { 349 | //console.log('buffer.node keypress', ev); 350 | if (ev.keyCode === 122 && ev.metaKey) { 351 | ev.preventDefault(); 352 | if (ev.shiftKey) { 353 | console.log('TODO: implement redo'); 354 | } else { 355 | console.log('TODO: implement undo'); 356 | } 357 | } 358 | }); 359 | } 360 | 361 | w.buffer.node.on('paste', function(ev) { 362 | // strip any non-plain text 363 | ev.clipboardData.types.filter(function(type){ 364 | return (type !== 'text/plain' && type !== 'public.utf8-plain-text'); 365 | }).forEach(function(type){ 366 | console.log('remove', type, ev.clipboardData.getData(type)); 367 | ev.clipboardData.clearData(type); 368 | }); 369 | }); 370 | 371 | w.buffer.node.on('blur', function(ev){ 372 | w.buffer.focused = false; 373 | w.buffer.saveSelection(); 374 | }); 375 | 376 | w.buffer.node.on('focus', function(){ 377 | w.buffer.focused = true; 378 | w.buffer.restoreSelection(); 379 | }); 380 | 381 | w.update(); 382 | w.buffer.select(); 383 | 384 | var bufferRedrawTimer, prevRaw = w.buffer.content; 385 | $(w.buffer.node).bind('keyup', function(){ 386 | w.buffer.saveSelectionRange(); 387 | 388 | // avoid triggering update of buffer display unless content changed 389 | var raw = w.buffer.content; 390 | if (!prevRaw || raw !== prevRaw) { 391 | console.log('buffer changed'); 392 | prevRaw = raw; 393 | // update gutter 394 | w.gutter.update(); 395 | // reset update timer 396 | clearTimeout(bufferRedrawTimer); 397 | bufferRedrawTimer = setTimeout(function(){ 398 | w.buffer.update(); 399 | prevRaw = w.buffer.content; 400 | }, 100); 401 | } 402 | }); 403 | 404 | // command line / minibuffer stuff -- area of TODO FIXME WIP and REFACTOR 405 | var $tf = $('#minibuffer input[type=text]'); 406 | var historyIndex = -1; 407 | $tf.keydown(function(ev) { 408 | if (ev.keyCode === 13) { 409 | var m, origInput = $.trim(this.value); 410 | if ((m = /([^\s]+)\s*(.*)/.exec(origInput))) { 411 | var cmd = exports.commands[m[1]]; 412 | if (cmd) { 413 | var rawArgs = m[2]; 414 | var args = rawArgs.split(/\s+/); 415 | args.unshift(rawArgs); 416 | 417 | // add to commandHistory 418 | var hist = localStorage.commandHistory; 419 | if (hist) { 420 | try { hist = JSON.parse(hist); } catch (e) { hist = []; } 421 | } else { 422 | hist = []; 423 | } 424 | if (hist[0] !== origInput) { 425 | hist.unshift(origInput); 426 | historyIndex = -1; 427 | hist = hist.unique(); 428 | if (hist.length > 100) { 429 | hist.splice(99, hist.length-99); 430 | } 431 | localStorage.commandHistory = JSON.stringify(hist); 432 | } 433 | 434 | // run command 435 | var output = cmd.apply(w.buffer, args); 436 | 437 | if (typeof output === 'string') { 438 | this.value = output; 439 | } 440 | } else { 441 | this.value = m[1]+': no such command. Try typing "help"'; 442 | } 443 | } else { 444 | console.log('command line: failed to parse input'); 445 | } 446 | this.select(); 447 | } else if (ev.keyCode === 38 || ev.keyCode === 40) { 448 | var hist = localStorage.commandHistory; 449 | hist = (hist) ? JSON.parse(hist) : null; 450 | if (hist && hist.length) { 451 | if (ev.keyCode === 38) { 452 | ++historyIndex; // up 453 | } else { 454 | --historyIndex; // down 455 | } 456 | if (historyIndex >= hist.length) { 457 | historyIndex = hist.length-1; 458 | $tf.addClass('highlight'); 459 | setTimeout(function(){ $tf.removeClass('highlight'); }, 100); 460 | } else { 461 | if (historyIndex < 0) historyIndex = -1; 462 | var s = hist[historyIndex]; 463 | //console.log(hist, historyIndex, '-->', s); 464 | this.value = s || ""; 465 | } 466 | } 467 | } 468 | }); 469 | $tf.focus(function(ev) { 470 | setTimeout(function(){ $tf[0].select(); },1); 471 | }); 472 | 473 | // document-wide keys 474 | document.on('keydown', function (ev) { 475 | if (ev.ctrlKey) { 476 | if (ev.keyCode === 88) { // C-x 477 | // switch to command line 478 | if (w.buffer.focused) { 479 | w.buffer.saveSelectionRange(); 480 | w.buffer.saveSelection(); 481 | } 482 | $tf.focus(); 483 | } 484 | } else if (ev.keyCode === 27) { // ESC 485 | if (!w.buffer.focused) { 486 | w.buffer.select(); 487 | w.buffer.restoreSelection(); 488 | } 489 | } 490 | //console.log(ev, ev.keyCode); 491 | }) 492 | 493 | // old super-simple token highlighter (WIP) 494 | /* 495 | function makeColorWrapper(color) { 496 | var n = document.createElement('span'); 497 | n.style.color = color; 498 | return n; 499 | } 500 | 501 | var range = document.createRange(); 502 | var tokenRE = /[\w\d]+/g, m, i=0; 503 | var colors = ['#f00','#ff0','#fff','#0ff','#00f','#f0f']; 504 | var guard = 400; 505 | while (guard--) { 506 | //while ((m = tokenRE.exec(buffer.textNode.data)) != null) { 507 | console.log(tokenRE.lastIndex); 508 | m = tokenRE.exec(w.buffer.textNode.data); 509 | var msg = "Found " + m[0] + ". "; 510 | msg += "Next match starts at " + tokenRE.lastIndex; 511 | 512 | range.setStart(w.buffer.textNode, m.index); 513 | range.setEnd(w.buffer.textNode, m.index + m[0].length); 514 | var wrapNode = makeColorWrapper(colors[i++ % colors.length]); 515 | range.surroundContents(wrapNode); 516 | console.log(msg); 517 | }*/ 518 | 519 | }); 520 | 521 | })(window.editor={}); // {exports} namespace 522 | -------------------------------------------------------------------------------- /prettify.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2006 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | /** 17 | * @fileoverview 18 | * some functions for browser-side pretty printing of code contained in html. 19 | *

20 | * 21 | * For a fairly comprehensive set of languages see the 22 | * README 23 | * file that came with this source. At a minimum, the lexer should work on a 24 | * number of languages including C and friends, Java, Python, Bash, SQL, HTML, 25 | * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk 26 | * and a subset of Perl, but, because of commenting conventions, doesn't work on 27 | * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. 28 | *

29 | * Usage:

    30 | *
  1. include this source file in an html page via 31 | * {@code } 32 | *
  2. define style rules. See the example page for examples. 33 | *
  3. mark the {@code
    } and {@code } tags in your source with
      34 |  *    {@code class=prettyprint.}
      35 |  *    You can also use the (html deprecated) {@code } tag, but the pretty
      36 |  *    printer needs to do more substantial DOM manipulations to support that, so
      37 |  *    some css styles may not be preserved.
      38 |  * </ol>
      39 |  * That's it.  I wanted to keep the API as simple as possible, so there's no
      40 |  * need to specify which language the code is in, but if you wish, you can add
      41 |  * another class to the {@code <pre>} or {@code <code>} element to specify the
      42 |  * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
      43 |  * starts with "lang-" followed by a file extension, specifies the file type.
      44 |  * See the "lang-*.js" files in this directory for code that implements
      45 |  * per-language file handlers.
      46 |  * <p>
      47 |  * Change log:<br>
      48 |  * cbeust, 2006/08/22
      49 |  * <blockquote>
      50 |  *   Java annotations (start with "@") are now captured as literals ("lit")
      51 |  * </blockquote>
      52 |  * @requires console
      53 |  * @overrides window
      54 |  */
      55 | 
      56 | // JSLint declarations
      57 | /*global console, document, navigator, setTimeout, window */
      58 | 
      59 | /**
      60 |  * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
      61 |  * UI events.
      62 |  * If set to {@code false}, {@code prettyPrint()} is synchronous.
      63 |  */
      64 | window['PR_SHOULD_USE_CONTINUATION'] = true;
      65 | 
      66 | /** the number of characters between tab columns */
      67 | window['PR_TAB_WIDTH'] = 8;
      68 | 
      69 | /** Walks the DOM returning a properly escaped version of innerHTML.
      70 |   * @param {Node} node
      71 |   * @param {Array.<string>} out output buffer that receives chunks of HTML.
      72 |   */
      73 | window['PR_normalizedHtml']
      74 | 
      75 | /** Contains functions for creating and registering new language handlers.
      76 |   * @type {Object}
      77 |   */
      78 |   = window['PR']
      79 | 
      80 | /** Pretty print a chunk of code.
      81 |   *
      82 |   * @param {string} sourceCodeHtml code as html
      83 |   * @return {string} code as html, but prettier
      84 |   */
      85 |   = window['prettyPrintOne']
      86 | /** Find all the {@code <pre>} and {@code <code>} tags in the DOM with
      87 |   * {@code class=prettyprint} and prettify them.
      88 |   * @param {Function?} opt_whenDone if specified, called when the last entry
      89 |   *     has been finished.
      90 |   */
      91 |   = window['prettyPrint'] = void 0;
      92 | 
      93 | /** browser detection. @extern @returns false if not IE, otherwise the major version. */
      94 | window['_pr_isIE6'] = function () {
      95 |   var ieVersion = navigator && navigator.userAgent &&
      96 |       navigator.userAgent.match(/\bMSIE ([678])\./);
      97 |   ieVersion = ieVersion ? +ieVersion[1] : false;
      98 |   window['_pr_isIE6'] = function () { return ieVersion; };
      99 |   return ieVersion;
     100 | };
     101 | 
     102 | 
     103 | (function () {
     104 |   // Keyword lists for various languages.
     105 |   var FLOW_CONTROL_KEYWORDS =
     106 |       "break continue do else for if return while ";
     107 |   var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
     108 |       "double enum extern float goto int long register short signed sizeof " +
     109 |       "static struct switch typedef union unsigned void volatile ";
     110 |   var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
     111 |       "new operator private protected public this throw true try typeof ";
     112 |   var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
     113 |       "concept concept_map const_cast constexpr decltype " +
     114 |       "dynamic_cast explicit export friend inline late_check " +
     115 |       "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
     116 |       "template typeid typename using virtual wchar_t where ";
     117 |   var JAVA_KEYWORDS = COMMON_KEYWORDS +
     118 |       "abstract boolean byte extends final finally implements import " +
     119 |       "instanceof null native package strictfp super synchronized throws " +
     120 |       "transient ";
     121 |   var CSHARP_KEYWORDS = JAVA_KEYWORDS +
     122 |       "as base by checked decimal delegate descending event " +
     123 |       "fixed foreach from group implicit in interface internal into is lock " +
     124 |       "object out override orderby params partial readonly ref sbyte sealed " +
     125 |       "stackalloc string select uint ulong unchecked unsafe ushort var ";
     126 |   var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
     127 |       "debugger eval export function get null set undefined var with " +
     128 |       "Infinity NaN ";
     129 |   var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
     130 |       "goto if import last local my next no our print package redo require " +
     131 |       "sub undef unless until use wantarray while BEGIN END ";
     132 |   var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
     133 |       "elif except exec finally from global import in is lambda " +
     134 |       "nonlocal not or pass print raise try with yield " +
     135 |       "False True None ";
     136 |   var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
     137 |       " defined elsif end ensure false in module next nil not or redo rescue " +
     138 |       "retry self super then true undef unless until when yield BEGIN END ";
     139 |   var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
     140 |       "function in local set then until ";
     141 |   var ALL_KEYWORDS = (
     142 |       CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
     143 |       PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
     144 | 
     145 |   // token style names.  correspond to css classes
     146 |   /** token style for a string literal */
     147 |   var PR_STRING = 'str';
     148 |   /** token style for a keyword */
     149 |   var PR_KEYWORD = 'kwd';
     150 |   /** token style for a comment */
     151 |   var PR_COMMENT = 'com';
     152 |   /** token style for a type */
     153 |   var PR_TYPE = 'typ';
     154 |   /** token style for a literal value.  e.g. 1, null, true. */
     155 |   var PR_LITERAL = 'lit';
     156 |   /** token style for a punctuation string. */
     157 |   var PR_PUNCTUATION = 'pun';
     158 |   /** token style for a punctuation string. */
     159 |   var PR_PLAIN = 'pln';
     160 | 
     161 |   /** token style for an sgml tag. */
     162 |   var PR_TAG = 'tag';
     163 |   /** token style for a markup declaration such as a DOCTYPE. */
     164 |   var PR_DECLARATION = 'dec';
     165 |   /** token style for embedded source. */
     166 |   var PR_SOURCE = 'src';
     167 |   /** token style for an sgml attribute name. */
     168 |   var PR_ATTRIB_NAME = 'atn';
     169 |   /** token style for an sgml attribute value. */
     170 |   var PR_ATTRIB_VALUE = 'atv';
     171 | 
     172 |   /**
     173 |    * A class that indicates a section of markup that is not code, e.g. to allow
     174 |    * embedding of line numbers within code listings.
     175 |    */
     176 |   var PR_NOCODE = 'nocode';
     177 | 
     178 |   /** A set of tokens that can precede a regular expression literal in
     179 |     * javascript.
     180 |     * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
     181 |     * list, but I've removed ones that might be problematic when seen in
     182 |     * languages that don't support regular expression literals.
     183 |     *
     184 |     * <p>Specifically, I've removed any keywords that can't precede a regexp
     185 |     * literal in a syntactically legal javascript program, and I've removed the
     186 |     * "in" keyword since it's not a keyword in many languages, and might be used
     187 |     * as a count of inches.
     188 |     *
     189 |     * <p>The link a above does not accurately describe EcmaScript rules since
     190 |     * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
     191 |     * very well in practice.
     192 |     *
     193 |     * @private
     194 |     */
     195 |   var REGEXP_PRECEDER_PATTERN = function () {
     196 |       var preceders = [
     197 |           "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
     198 |           "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
     199 |           "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
     200 |           "<", "<<", "<<=", "<=", "=", "==", "===", ">",
     201 |           ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
     202 |           "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
     203 |           "||=", "~" /* handles =~ and !~ */,
     204 |           "break", "case", "continue", "delete",
     205 |           "do", "else", "finally", "instanceof",
     206 |           "return", "throw", "try", "typeof"
     207 |           ];
     208 |       var pattern = '(?:^^|[+-]';
     209 |       for (var i = 0; i < preceders.length; ++i) {
     210 |         pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
     211 |       }
     212 |       pattern += ')\\s*';  // matches at end, and matches empty string
     213 |       return pattern;
     214 |       // CAVEAT: this does not properly handle the case where a regular
     215 |       // expression immediately follows another since a regular expression may
     216 |       // have flags for case-sensitivity and the like.  Having regexp tokens
     217 |       // adjacent is not valid in any language I'm aware of, so I'm punting.
     218 |       // TODO: maybe style special characters inside a regexp as punctuation.
     219 |     }();
     220 | 
     221 |   // Define regexps here so that the interpreter doesn't have to create an
     222 |   // object each time the function containing them is called.
     223 |   // The language spec requires a new object created even if you don't access
     224 |   // the $1 members.
     225 |   var pr_amp = /&/g;
     226 |   var pr_lt = /</g;
     227 |   var pr_gt = />/g;
     228 |   var pr_quot = /\"/g;
     229 |   /** like textToHtml but escapes double quotes to be attribute safe. */
     230 |   function attribToHtml(str) {
     231 |     return str.replace(pr_amp, '&amp;')
     232 |         .replace(pr_lt, '&lt;')
     233 |         .replace(pr_gt, '&gt;')
     234 |         .replace(pr_quot, '&quot;');
     235 |   }
     236 | 
     237 |   /** escapest html special characters to html. */
     238 |   function textToHtml(str) {
     239 |     return str.replace(pr_amp, '&amp;')
     240 |         .replace(pr_lt, '&lt;')
     241 |         .replace(pr_gt, '&gt;');
     242 |   }
     243 | 
     244 | 
     245 |   var pr_ltEnt = /&lt;/g;
     246 |   var pr_gtEnt = /&gt;/g;
     247 |   var pr_aposEnt = /&apos;/g;
     248 |   var pr_quotEnt = /&quot;/g;
     249 |   var pr_ampEnt = /&amp;/g;
     250 |   var pr_nbspEnt = /&nbsp;/g;
     251 |   /** unescapes html to plain text. */
     252 |   function htmlToText(html) {
     253 |     var pos = html.indexOf('&');
     254 |     if (pos < 0) { return html; }
     255 |     // Handle numeric entities specially.  We can't use functional substitution
     256 |     // since that doesn't work in older versions of Safari.
     257 |     // These should be rare since most browsers convert them to normal chars.
     258 |     for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0;) {
     259 |       var end = html.indexOf(';', pos);
     260 |       if (end >= 0) {
     261 |         var num = html.substring(pos + 3, end);
     262 |         var radix = 10;
     263 |         if (num && num.charAt(0) === 'x') {
     264 |           num = num.substring(1);
     265 |           radix = 16;
     266 |         }
     267 |         var codePoint = parseInt(num, radix);
     268 |         if (!isNaN(codePoint)) {
     269 |           html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
     270 |                   html.substring(end + 1));
     271 |         }
     272 |       }
     273 |     }
     274 | 
     275 |     return html.replace(pr_ltEnt, '<')
     276 |         .replace(pr_gtEnt, '>')
     277 |         .replace(pr_aposEnt, "'")
     278 |         .replace(pr_quotEnt, '"')
     279 |         .replace(pr_nbspEnt, ' ')
     280 |         .replace(pr_ampEnt, '&');
     281 |   }
     282 | 
     283 |   /** is the given node's innerHTML normally unescaped? */
     284 |   function isRawContent(node) {
     285 |     return 'XMP' === node.tagName;
     286 |   }
     287 | 
     288 |   var newlineRe = /[\r\n]/g;
     289 |   /**
     290 |    * Are newlines and adjacent spaces significant in the given node's innerHTML?
     291 |    */
     292 |   function isPreformatted(node, content) {
     293 |     // PRE means preformatted, and is a very common case, so don't create
     294 |     // unnecessary computed style objects.
     295 |     if ('PRE' === node.tagName) { return true; }
     296 |     if (!newlineRe.test(content)) { return true; }  // Don't care
     297 |     var whitespace = '';
     298 |     // For disconnected nodes, IE has no currentStyle.
     299 |     if (node.currentStyle) {
     300 |       whitespace = node.currentStyle.whiteSpace;
     301 |     } else if (window.getComputedStyle) {
     302 |       // Firefox makes a best guess if node is disconnected whereas Safari
     303 |       // returns the empty string.
     304 |       whitespace = window.getComputedStyle(node, null).whiteSpace;
     305 |     }
     306 |     return !whitespace || whitespace === 'pre';
     307 |   }
     308 | 
     309 |   function normalizedHtml(node, out, opt_sortAttrs) {
     310 |     switch (node.nodeType) {
     311 |       case 1:  // an element
     312 |         var name = node.tagName.toLowerCase();
     313 | 
     314 |         out.push('<', name);
     315 |         var attrs = node.attributes;
     316 |         var n = attrs.length;
     317 |         if (n) {
     318 |           if (opt_sortAttrs) {
     319 |             var sortedAttrs = [];
     320 |             for (var i = n; --i >= 0;) { sortedAttrs[i] = attrs[i]; }
     321 |             sortedAttrs.sort(function (a, b) {
     322 |                 return (a.name < b.name) ? -1 : a.name === b.name ? 0 : 1;
     323 |               });
     324 |             attrs = sortedAttrs;
     325 |           }
     326 |           for (var i = 0; i < n; ++i) {
     327 |             var attr = attrs[i];
     328 |             if (!attr.specified) { continue; }
     329 |             out.push(' ', attr.name.toLowerCase(),
     330 |                      '="', attribToHtml(attr.value), '"');
     331 |           }
     332 |         }
     333 |         out.push('>');
     334 |         for (var child = node.firstChild; child; child = child.nextSibling) {
     335 |           normalizedHtml(child, out, opt_sortAttrs);
     336 |         }
     337 |         if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
     338 |           out.push('<\/', name, '>');
     339 |         }
     340 |         break;
     341 |       case 3: case 4: // text
     342 |         out.push(textToHtml(node.nodeValue));
     343 |         break;
     344 |     }
     345 |   }
     346 | 
     347 |   /**
     348 |    * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
     349 |    * matches the union o the sets o strings matched d by the input RegExp.
     350 |    * Since it matches globally, if the input strings have a start-of-input
     351 |    * anchor (/^.../), it is ignored for the purposes of unioning.
     352 |    * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
     353 |    * @return {RegExp} a global regex.
     354 |    */
     355 |   function combinePrefixPatterns(regexs) {
     356 |     var capturedGroupIndex = 0;
     357 | 
     358 |     var needToFoldCase = false;
     359 |     var ignoreCase = false;
     360 |     for (var i = 0, n = regexs.length; i < n; ++i) {
     361 |       var regex = regexs[i];
     362 |       if (regex.ignoreCase) {
     363 |         ignoreCase = true;
     364 |       } else if (/[a-z]/i.test(regex.source.replace(
     365 |                      /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
     366 |         needToFoldCase = true;
     367 |         ignoreCase = false;
     368 |         break;
     369 |       }
     370 |     }
     371 | 
     372 |     function decodeEscape(charsetPart) {
     373 |       if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
     374 |       switch (charsetPart.charAt(1)) {
     375 |         case 'b': return 8;
     376 |         case 't': return 9;
     377 |         case 'n': return 0xa;
     378 |         case 'v': return 0xb;
     379 |         case 'f': return 0xc;
     380 |         case 'r': return 0xd;
     381 |         case 'u': case 'x':
     382 |           return parseInt(charsetPart.substring(2), 16)
     383 |               || charsetPart.charCodeAt(1);
     384 |         case '0': case '1': case '2': case '3': case '4':
     385 |         case '5': case '6': case '7':
     386 |           return parseInt(charsetPart.substring(1), 8);
     387 |         default: return charsetPart.charCodeAt(1);
     388 |       }
     389 |     }
     390 | 
     391 |     function encodeEscape(charCode) {
     392 |       if (charCode < 0x20) {
     393 |         return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
     394 |       }
     395 |       var ch = String.fromCharCode(charCode);
     396 |       if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
     397 |         ch = '\\' + ch;
     398 |       }
     399 |       return ch;
     400 |     }
     401 | 
     402 |     function caseFoldCharset(charSet) {
     403 |       var charsetParts = charSet.substring(1, charSet.length - 1).match(
     404 |           new RegExp(
     405 |               '\\\\u[0-9A-Fa-f]{4}'
     406 |               + '|\\\\x[0-9A-Fa-f]{2}'
     407 |               + '|\\\\[0-3][0-7]{0,2}'
     408 |               + '|\\\\[0-7]{1,2}'
     409 |               + '|\\\\[\\s\\S]'
     410 |               + '|-'
     411 |               + '|[^-\\\\]',
     412 |               'g'));
     413 |       var groups = [];
     414 |       var ranges = [];
     415 |       var inverse = charsetParts[0] === '^';
     416 |       for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
     417 |         var p = charsetParts[i];
     418 |         switch (p) {
     419 |           case '\\B': case '\\b':
     420 |           case '\\D': case '\\d':
     421 |           case '\\S': case '\\s':
     422 |           case '\\W': case '\\w':
     423 |             groups.push(p);
     424 |             continue;
     425 |         }
     426 |         var start = decodeEscape(p);
     427 |         var end;
     428 |         if (i + 2 < n && '-' === charsetParts[i + 1]) {
     429 |           end = decodeEscape(charsetParts[i + 2]);
     430 |           i += 2;
     431 |         } else {
     432 |           end = start;
     433 |         }
     434 |         ranges.push([start, end]);
     435 |         // If the range might intersect letters, then expand it.
     436 |         if (!(end < 65 || start > 122)) {
     437 |           if (!(end < 65 || start > 90)) {
     438 |             ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
     439 |           }
     440 |           if (!(end < 97 || start > 122)) {
     441 |             ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
     442 |           }
     443 |         }
     444 |       }
     445 | 
     446 |       // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
     447 |       // -> [[1, 12], [14, 14], [16, 17]]
     448 |       ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
     449 |       var consolidatedRanges = [];
     450 |       var lastRange = [NaN, NaN];
     451 |       for (var i = 0; i < ranges.length; ++i) {
     452 |         var range = ranges[i];
     453 |         if (range[0] <= lastRange[1] + 1) {
     454 |           lastRange[1] = Math.max(lastRange[1], range[1]);
     455 |         } else {
     456 |           consolidatedRanges.push(lastRange = range);
     457 |         }
     458 |       }
     459 | 
     460 |       var out = ['['];
     461 |       if (inverse) { out.push('^'); }
     462 |       out.push.apply(out, groups);
     463 |       for (var i = 0; i < consolidatedRanges.length; ++i) {
     464 |         var range = consolidatedRanges[i];
     465 |         out.push(encodeEscape(range[0]));
     466 |         if (range[1] > range[0]) {
     467 |           if (range[1] + 1 > range[0]) { out.push('-'); }
     468 |           out.push(encodeEscape(range[1]));
     469 |         }
     470 |       }
     471 |       out.push(']');
     472 |       return out.join('');
     473 |     }
     474 | 
     475 |     function allowAnywhereFoldCaseAndRenumberGroups(regex) {
     476 |       // Split into character sets, escape sequences, punctuation strings
     477 |       // like ('(', '(?:', ')', '^'), and runs of characters that do not
     478 |       // include any of the above.
     479 |       var parts = regex.source.match(
     480 |           new RegExp(
     481 |               '(?:'
     482 |               + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
     483 |               + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
     484 |               + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
     485 |               + '|\\\\[0-9]+'  // a back-reference or octal escape
     486 |               + '|\\\\[^ux0-9]'  // other escape sequence
     487 |               + '|\\(\\?[:!=]'  // start of a non-capturing group
     488 |               + '|[\\(\\)\\^]'  // start/emd of a group, or line start
     489 |               + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
     490 |               + ')',
     491 |               'g'));
     492 |       var n = parts.length;
     493 | 
     494 |       // Maps captured group numbers to the number they will occupy in
     495 |       // the output or to -1 if that has not been determined, or to
     496 |       // undefined if they need not be capturing in the output.
     497 |       var capturedGroups = [];
     498 | 
     499 |       // Walk over and identify back references to build the capturedGroups
     500 |       // mapping.
     501 |       for (var i = 0, groupIndex = 0; i < n; ++i) {
     502 |         var p = parts[i];
     503 |         if (p === '(') {
     504 |           // groups are 1-indexed, so max group index is count of '('
     505 |           ++groupIndex;
     506 |         } else if ('\\' === p.charAt(0)) {
     507 |           var decimalValue = +p.substring(1);
     508 |           if (decimalValue && decimalValue <= groupIndex) {
     509 |             capturedGroups[decimalValue] = -1;
     510 |           }
     511 |         }
     512 |       }
     513 | 
     514 |       // Renumber groups and reduce capturing groups to non-capturing groups
     515 |       // where possible.
     516 |       for (var i = 1; i < capturedGroups.length; ++i) {
     517 |         if (-1 === capturedGroups[i]) {
     518 |           capturedGroups[i] = ++capturedGroupIndex;
     519 |         }
     520 |       }
     521 |       for (var i = 0, groupIndex = 0; i < n; ++i) {
     522 |         var p = parts[i];
     523 |         if (p === '(') {
     524 |           ++groupIndex;
     525 |           if (capturedGroups[groupIndex] === undefined) {
     526 |             parts[i] = '(?:';
     527 |           }
     528 |         } else if ('\\' === p.charAt(0)) {
     529 |           var decimalValue = +p.substring(1);
     530 |           if (decimalValue && decimalValue <= groupIndex) {
     531 |             parts[i] = '\\' + capturedGroups[groupIndex];
     532 |           }
     533 |         }
     534 |       }
     535 | 
     536 |       // Remove any prefix anchors so that the output will match anywhere.
     537 |       // ^^ really does mean an anchored match though.
     538 |       for (var i = 0, groupIndex = 0; i < n; ++i) {
     539 |         if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
     540 |       }
     541 | 
     542 |       // Expand letters to groupts to handle mixing of case-sensitive and
     543 |       // case-insensitive patterns if necessary.
     544 |       if (regex.ignoreCase && needToFoldCase) {
     545 |         for (var i = 0; i < n; ++i) {
     546 |           var p = parts[i];
     547 |           var ch0 = p.charAt(0);
     548 |           if (p.length >= 2 && ch0 === '[') {
     549 |             parts[i] = caseFoldCharset(p);
     550 |           } else if (ch0 !== '\\') {
     551 |             // TODO: handle letters in numeric escapes.
     552 |             parts[i] = p.replace(
     553 |                 /[a-zA-Z]/g,
     554 |                 function (ch) {
     555 |                   var cc = ch.charCodeAt(0);
     556 |                   return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
     557 |                 });
     558 |           }
     559 |         }
     560 |       }
     561 | 
     562 |       return parts.join('');
     563 |     }
     564 | 
     565 |     var rewritten = [];
     566 |     for (var i = 0, n = regexs.length; i < n; ++i) {
     567 |       var regex = regexs[i];
     568 |       if (regex.global || regex.multiline) { throw new Error('' + regex); }
     569 |       rewritten.push(
     570 |           '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
     571 |     }
     572 | 
     573 |     return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
     574 |   }
     575 | 
     576 |   var PR_innerHtmlWorks = null;
     577 |   function getInnerHtml(node) {
     578 |     // inner html is hopelessly broken in Safari 2.0.4 when the content is
     579 |     // an html description of well formed XML and the containing tag is a PRE
     580 |     // tag, so we detect that case and emulate innerHTML.
     581 |     if (null === PR_innerHtmlWorks) {
     582 |       var testNode = document.createElement('PRE');
     583 |       testNode.appendChild(
     584 |           document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));
     585 |       PR_innerHtmlWorks = !/</.test(testNode.innerHTML);
     586 |     }
     587 | 
     588 |     if (PR_innerHtmlWorks) {
     589 |       var content = node.innerHTML;
     590 |       // XMP tags contain unescaped entities so require special handling.
     591 |       if (isRawContent(node)) {
     592 |         content = textToHtml(content);
     593 |       } else if (!isPreformatted(node, content)) {
     594 |         content = content.replace(/(<br\s*\/?>)[\r\n]+/g, '$1')
     595 |             .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
     596 |       }
     597 |       return content;
     598 |     }
     599 | 
     600 |     var out = [];
     601 |     for (var child = node.firstChild; child; child = child.nextSibling) {
     602 |       normalizedHtml(child, out);
     603 |     }
     604 |     return out.join('');
     605 |   }
     606 | 
     607 |   /** returns a function that expand tabs to spaces.  This function can be fed
     608 |     * successive chunks of text, and will maintain its own internal state to
     609 |     * keep track of how tabs are expanded.
     610 |     * @return {function (string) : string} a function that takes
     611 |     *   plain text and return the text with tabs expanded.
     612 |     * @private
     613 |     */
     614 |   function makeTabExpander(tabWidth) {
     615 |     var SPACES = '                ';
     616 |     var charInLine = 0;
     617 | 
     618 |     return function (plainText) {
     619 |       // walk over each character looking for tabs and newlines.
     620 |       // On tabs, expand them.  On newlines, reset charInLine.
     621 |       // Otherwise increment charInLine
     622 |       var out = null;
     623 |       var pos = 0;
     624 |       for (var i = 0, n = plainText.length; i < n; ++i) {
     625 |         var ch = plainText.charAt(i);
     626 | 
     627 |         switch (ch) {
     628 |           case '\t':
     629 |             if (!out) { out = []; }
     630 |             out.push(plainText.substring(pos, i));
     631 |             // calculate how much space we need in front of this part
     632 |             // nSpaces is the amount of padding -- the number of spaces needed
     633 |             // to move us to the next column, where columns occur at factors of
     634 |             // tabWidth.
     635 |             var nSpaces = tabWidth - (charInLine % tabWidth);
     636 |             charInLine += nSpaces;
     637 |             for (; nSpaces >= 0; nSpaces -= SPACES.length) {
     638 |               out.push(SPACES.substring(0, nSpaces));
     639 |             }
     640 |             pos = i + 1;
     641 |             break;
     642 |           case '\n':
     643 |             charInLine = 0;
     644 |             break;
     645 |           default:
     646 |             ++charInLine;
     647 |         }
     648 |       }
     649 |       if (!out) { return plainText; }
     650 |       out.push(plainText.substring(pos));
     651 |       return out.join('');
     652 |     };
     653 |   }
     654 | 
     655 |   var pr_chunkPattern = new RegExp(
     656 |       '[^<]+'  // A run of characters other than '<'
     657 |       + '|<\!--[\\s\\S]*?--\>'  // an HTML comment
     658 |       + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'  // a CDATA section
     659 |       // a probable tag that should not be highlighted
     660 |       + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
     661 |       + '|<',  // A '<' that does not begin a larger chunk
     662 |       'g');
     663 |   var pr_commentPrefix = /^<\!--/;
     664 |   var pr_cdataPrefix = /^<!\[CDATA\[/;
     665 |   var pr_brPrefix = /^<br\b/i;
     666 |   var pr_tagNameRe = /^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/;
     667 | 
     668 |   /** split markup into chunks of html tags (style null) and
     669 |     * plain text (style {@link #PR_PLAIN}), converting tags which are
     670 |     * significant for tokenization (<br>) into their textual equivalent.
     671 |     *
     672 |     * @param {string} s html where whitespace is considered significant.
     673 |     * @return {Object} source code and extracted tags.
     674 |     * @private
     675 |     */
     676 |   function extractTags(s) {
     677 |     // since the pattern has the 'g' modifier and defines no capturing groups,
     678 |     // this will return a list of all chunks which we then classify and wrap as
     679 |     // PR_Tokens
     680 |     var matches = s.match(pr_chunkPattern);
     681 |     var sourceBuf = [];
     682 |     var sourceBufLen = 0;
     683 |     var extractedTags = [];
     684 |     if (matches) {
     685 |       for (var i = 0, n = matches.length; i < n; ++i) {
     686 |         var match = matches[i];
     687 |         if (match.length > 1 && match.charAt(0) === '<') {
     688 |           if (pr_commentPrefix.test(match)) { continue; }
     689 |           if (pr_cdataPrefix.test(match)) {
     690 |             // strip CDATA prefix and suffix.  Don't unescape since it's CDATA
     691 |             sourceBuf.push(match.substring(9, match.length - 3));
     692 |             sourceBufLen += match.length - 12;
     693 |           } else if (pr_brPrefix.test(match)) {
     694 |             // <br> tags are lexically significant so convert them to text.
     695 |             // This is undone later.
     696 |             sourceBuf.push('\n');
     697 |             ++sourceBufLen;
     698 |           } else {
     699 |             if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
     700 |               // A <span class="nocode"> will start a section that should be
     701 |               // ignored.  Continue walking the list until we see a matching end
     702 |               // tag.
     703 |               var name = match.match(pr_tagNameRe)[2];
     704 |               var depth = 1;
     705 |               var j;
     706 |               end_tag_loop:
     707 |               for (j = i + 1; j < n; ++j) {
     708 |                 var name2 = matches[j].match(pr_tagNameRe);
     709 |                 if (name2 && name2[2] === name) {
     710 |                   if (name2[1] === '/') {
     711 |                     if (--depth === 0) { break end_tag_loop; }
     712 |                   } else {
     713 |                     ++depth;
     714 |                   }
     715 |                 }
     716 |               }
     717 |               if (j < n) {
     718 |                 extractedTags.push(
     719 |                     sourceBufLen, matches.slice(i, j + 1).join(''));
     720 |                 i = j;
     721 |               } else {  // Ignore unclosed sections.
     722 |                 extractedTags.push(sourceBufLen, match);
     723 |               }
     724 |             } else {
     725 |               extractedTags.push(sourceBufLen, match);
     726 |             }
     727 |           }
     728 |         } else {
     729 |           var literalText = htmlToText(match);
     730 |           sourceBuf.push(literalText);
     731 |           sourceBufLen += literalText.length;
     732 |         }
     733 |       }
     734 |     }
     735 |     return { source: sourceBuf.join(''), tags: extractedTags };
     736 |   }
     737 | 
     738 |   /** True if the given tag contains a class attribute with the nocode class. */
     739 |   function isNoCodeTag(tag) {
     740 |     return !!tag
     741 |         // First canonicalize the representation of attributes
     742 |         .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
     743 |                  ' $1="$2$3$4"')
     744 |         // Then look for the attribute we want.
     745 |         .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
     746 |   }
     747 | 
     748 |   /**
     749 |    * Apply the given language handler to sourceCode and add the resulting
     750 |    * decorations to out.
     751 |    * @param {number} basePos the index of sourceCode within the chunk of source
     752 |    *    whose decorations are already present on out.
     753 |    */
     754 |   function appendDecorations(basePos, sourceCode, langHandler, out) {
     755 |     if (!sourceCode) { return; }
     756 |     var job = {
     757 |       source: sourceCode,
     758 |       basePos: basePos
     759 |     };
     760 |     langHandler(job);
     761 |     out.push.apply(out, job.decorations);
     762 |   }
     763 | 
     764 |   /** Given triples of [style, pattern, context] returns a lexing function,
     765 |     * The lexing function interprets the patterns to find token boundaries and
     766 |     * returns a decoration list of the form
     767 |     * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
     768 |     * where index_n is an index into the sourceCode, and style_n is a style
     769 |     * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
     770 |     * all characters in sourceCode[index_n-1:index_n].
     771 |     *
     772 |     * The stylePatterns is a list whose elements have the form
     773 |     * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
     774 |     *
     775 |     * Style is a style constant like PR_PLAIN, or can be a string of the
     776 |     * form 'lang-FOO', where FOO is a language extension describing the
     777 |     * language of the portion of the token in $1 after pattern executes.
     778 |     * E.g., if style is 'lang-lisp', and group 1 contains the text
     779 |     * '(hello (world))', then that portion of the token will be passed to the
     780 |     * registered lisp handler for formatting.
     781 |     * The text before and after group 1 will be restyled using this decorator
     782 |     * so decorators should take care that this doesn't result in infinite
     783 |     * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
     784 |     * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
     785 |     * '<script>foo()<\/script>', which would cause the current decorator to
     786 |     * be called with '<script>' which would not match the same rule since
     787 |     * group 1 must not be empty, so it would be instead styled as PR_TAG by
     788 |     * the generic tag rule.  The handler registered for the 'js' extension would
     789 |     * then be called with 'foo()', and finally, the current decorator would
     790 |     * be called with '<\/script>' which would not match the original rule and
     791 |     * so the generic tag rule would identify it as a tag.
     792 |     *
     793 |     * Pattern must only match prefixes, and if it matches a prefix, then that
     794 |     * match is considered a token with the same style.
     795 |     *
     796 |     * Context is applied to the last non-whitespace, non-comment token
     797 |     * recognized.
     798 |     *
     799 |     * Shortcut is an optional string of characters, any of which, if the first
     800 |     * character, gurantee that this pattern and only this pattern matches.
     801 |     *
     802 |     * @param {Array} shortcutStylePatterns patterns that always start with
     803 |     *   a known character.  Must have a shortcut string.
     804 |     * @param {Array} fallthroughStylePatterns patterns that will be tried in
     805 |     *   order if the shortcut ones fail.  May have shortcuts.
     806 |     *
     807 |     * @return {function (Object)} a
     808 |     *   function that takes source code and returns a list of decorations.
     809 |     */
     810 |   function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
     811 |     var shortcuts = {};
     812 |     var tokenizer;
     813 |     (function () {
     814 |       var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
     815 |       var allRegexs = [];
     816 |       var regexKeys = {};
     817 |       for (var i = 0, n = allPatterns.length; i < n; ++i) {
     818 |         var patternParts = allPatterns[i];
     819 |         var shortcutChars = patternParts[3];
     820 |         if (shortcutChars) {
     821 |           for (var c = shortcutChars.length; --c >= 0;) {
     822 |             shortcuts[shortcutChars.charAt(c)] = patternParts;
     823 |           }
     824 |         }
     825 |         var regex = patternParts[1];
     826 |         var k = '' + regex;
     827 |         if (!regexKeys.hasOwnProperty(k)) {
     828 |           allRegexs.push(regex);
     829 |           regexKeys[k] = null;
     830 |         }
     831 |       }
     832 |       allRegexs.push(/[\0-\uffff]/);
     833 |       tokenizer = combinePrefixPatterns(allRegexs);
     834 |     })();
     835 | 
     836 |     var nPatterns = fallthroughStylePatterns.length;
     837 |     var notWs = /\S/;
     838 | 
     839 |     /**
     840 |      * Lexes job.source and produces an output array job.decorations of style
     841 |      * classes preceded by the position at which they start in job.source in
     842 |      * order.
     843 |      *
     844 |      * @param {Object} job an object like {@code
     845 |      *    source: {string} sourceText plain text,
     846 |      *    basePos: {int} position of job.source in the larger chunk of
     847 |      *        sourceCode.
     848 |      * }
     849 |      */
     850 |     var decorate = function (job) {
     851 |       var sourceCode = job.source, basePos = job.basePos;
     852 |       /** Even entries are positions in source in ascending order.  Odd enties
     853 |         * are style markers (e.g., PR_COMMENT) that run from that position until
     854 |         * the end.
     855 |         * @type {Array.<number|string>}
     856 |         */
     857 |       var decorations = [basePos, PR_PLAIN];
     858 |       var pos = 0;  // index into sourceCode
     859 |       var tokens = sourceCode.match(tokenizer) || [];
     860 |       var styleCache = {};
     861 | 
     862 |       for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
     863 |         var token = tokens[ti];
     864 |         var style = styleCache[token];
     865 |         var match = void 0;
     866 | 
     867 |         var isEmbedded;
     868 |         if (typeof style === 'string') {
     869 |           isEmbedded = false;
     870 |         } else {
     871 |           var patternParts = shortcuts[token.charAt(0)];
     872 |           if (patternParts) {
     873 |             match = token.match(patternParts[1]);
     874 |             style = patternParts[0];
     875 |           } else {
     876 |             for (var i = 0; i < nPatterns; ++i) {
     877 |               patternParts = fallthroughStylePatterns[i];
     878 |               match = token.match(patternParts[1]);
     879 |               if (match) {
     880 |                 style = patternParts[0];
     881 |                 break;
     882 |               }
     883 |             }
     884 | 
     885 |             if (!match) {  // make sure that we make progress
     886 |               style = PR_PLAIN;
     887 |             }
     888 |           }
     889 | 
     890 |           isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
     891 |           if (isEmbedded && !(match && typeof match[1] === 'string')) {
     892 |             isEmbedded = false;
     893 |             style = PR_SOURCE;
     894 |           }
     895 | 
     896 |           if (!isEmbedded) { styleCache[token] = style; }
     897 |         }
     898 | 
     899 |         var tokenStart = pos;
     900 |         pos += token.length;
     901 | 
     902 |         if (!isEmbedded) {
     903 |           decorations.push(basePos + tokenStart, style);
     904 |         } else {  // Treat group 1 as an embedded block of source code.
     905 |           var embeddedSource = match[1];
     906 |           var embeddedSourceStart = token.indexOf(embeddedSource);
     907 |           var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
     908 |           if (match[2]) {
     909 |             // If embeddedSource can be blank, then it would match at the
     910 |             // beginning which would cause us to infinitely recurse on the
     911 |             // entire token, so we catch the right context in match[2].
     912 |             embeddedSourceEnd = token.length - match[2].length;
     913 |             embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
     914 |           }
     915 |           var lang = style.substring(5);
     916 |           // Decorate the left of the embedded source
     917 |           appendDecorations(
     918 |               basePos + tokenStart,
     919 |               token.substring(0, embeddedSourceStart),
     920 |               decorate, decorations);
     921 |           // Decorate the embedded source
     922 |           appendDecorations(
     923 |               basePos + tokenStart + embeddedSourceStart,
     924 |               embeddedSource,
     925 |               langHandlerForExtension(lang, embeddedSource),
     926 |               decorations);
     927 |           // Decorate the right of the embedded section
     928 |           appendDecorations(
     929 |               basePos + tokenStart + embeddedSourceEnd,
     930 |               token.substring(embeddedSourceEnd),
     931 |               decorate, decorations);
     932 |         }
     933 |       }
     934 |       job.decorations = decorations;
     935 |     };
     936 |     return decorate;
     937 |   }
     938 | 
     939 |   /** returns a function that produces a list of decorations from source text.
     940 |     *
     941 |     * This code treats ", ', and ` as string delimiters, and \ as a string
     942 |     * escape.  It does not recognize perl's qq() style strings.
     943 |     * It has no special handling for double delimiter escapes as in basic, or
     944 |     * the tripled delimiters used in python, but should work on those regardless
     945 |     * although in those cases a single string literal may be broken up into
     946 |     * multiple adjacent string literals.
     947 |     *
     948 |     * It recognizes C, C++, and shell style comments.
     949 |     *
     950 |     * @param {Object} options a set of optional parameters.
     951 |     * @return {function (Object)} a function that examines the source code
     952 |     *     in the input job and builds the decoration list.
     953 |     */
     954 |   function sourceDecorator(options) {
     955 |     var shortcutStylePatterns = [], fallthroughStylePatterns = [];
     956 |     if (options['tripleQuotedStrings']) {
     957 |       // '''multi-line-string''', 'single-line-string', and double-quoted
     958 |       shortcutStylePatterns.push(
     959 |           [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
     960 |            null, '\'"']);
     961 |     } else if (options['multiLineStrings']) {
     962 |       // 'multi-line-string', "multi-line-string"
     963 |       shortcutStylePatterns.push(
     964 |           [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
     965 |            null, '\'"`']);
     966 |     } else {
     967 |       // 'single-line-string', "single-line-string"
     968 |       shortcutStylePatterns.push(
     969 |           [PR_STRING,
     970 |            /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
     971 |            null, '"\'']);
     972 |     }
     973 |     if (options['verbatimStrings']) {
     974 |       // verbatim-string-literal production from the C# grammar.  See issue 93.
     975 |       fallthroughStylePatterns.push(
     976 |           [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
     977 |     }
     978 |     if (options['hashComments']) {
     979 |       if (options['cStyleComments']) {
     980 |         // Stop C preprocessor declarations at an unclosed open comment
     981 |         shortcutStylePatterns.push(
     982 |             [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
     983 |              null, '#']);
     984 |         fallthroughStylePatterns.push(
     985 |             [PR_STRING,
     986 |              /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
     987 |              null]);
     988 |       } else {
     989 |         shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
     990 |       }
     991 |     }
     992 |     if (options['cStyleComments']) {
     993 |       fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
     994 |       fallthroughStylePatterns.push(
     995 |           [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
     996 |     }
     997 |     if (options['regexLiterals']) {
     998 |       var REGEX_LITERAL = (
     999 |           // A regular expression literal starts with a slash that is
    1000 |           // not followed by * or / so that it is not confused with
    1001 |           // comments.
    1002 |           '/(?=[^/*])'
    1003 |           // and then contains any number of raw characters,
    1004 |           + '(?:[^/\\x5B\\x5C]'
    1005 |           // escape sequences (\x5C),
    1006 |           +    '|\\x5C[\\s\\S]'
    1007 |           // or non-nesting character sets (\x5B\x5D);
    1008 |           +    '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
    1009 |           // finally closed by a /.
    1010 |           + '/');
    1011 |       fallthroughStylePatterns.push(
    1012 |           ['lang-regex',
    1013 |            new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
    1014 |            ]);
    1015 |     }
    1016 | 
    1017 |     var keywords = options['keywords'].replace(/^\s+|\s+$/g, '');
    1018 |     if (keywords.length) {
    1019 |       fallthroughStylePatterns.push(
    1020 |           [PR_KEYWORD,
    1021 |            new RegExp('^(?:' + keywords.replace(/\s+/g, '|') + ')\\b'), null]);
    1022 |     }
    1023 | 
    1024 |     shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
    1025 |     fallthroughStylePatterns.push(
    1026 |         // TODO(mikesamuel): recognize non-latin letters and numerals in idents
    1027 |         [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
    1028 |         [PR_TYPE,        /^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, null],
    1029 |         [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
    1030 |         [PR_LITERAL,
    1031 |          new RegExp(
    1032 |              '^(?:'
    1033 |              // A hex number
    1034 |              + '0x[a-f0-9]+'
    1035 |              // or an octal or decimal number,
    1036 |              + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
    1037 |              // possibly in scientific notation
    1038 |              + '(?:e[+\\-]?\\d+)?'
    1039 |              + ')'
    1040 |              // with an optional modifier like UL for unsigned long
    1041 |              + '[a-z]*', 'i'),
    1042 |          null, '0123456789'],
    1043 |         [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#]*/, null]);
    1044 | 
    1045 |     return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
    1046 |   }
    1047 | 
    1048 |   var decorateSource = sourceDecorator({
    1049 |         'keywords': ALL_KEYWORDS,
    1050 |         'hashComments': true,
    1051 |         'cStyleComments': true,
    1052 |         'multiLineStrings': true,
    1053 |         'regexLiterals': true
    1054 |       });
    1055 | 
    1056 |   /** Breaks {@code job.source} around style boundaries in
    1057 |     * {@code job.decorations} while re-interleaving {@code job.extractedTags},
    1058 |     * and leaves the result in {@code job.prettyPrintedHtml}.
    1059 |     * @param {Object} job like {
    1060 |     *    source: {string} source as plain text,
    1061 |     *    extractedTags: {Array.<number|string>} extractedTags chunks of raw
    1062 |     *                   html preceded by their position in {@code job.source}
    1063 |     *                   in order
    1064 |     *    decorations: {Array.<number|string} an array of style classes preceded
    1065 |     *                 by the position at which they start in job.source in order
    1066 |     * }
    1067 |     * @private
    1068 |     */
    1069 |   function recombineTagsAndDecorations(job) {
    1070 |     var sourceText = job.source;
    1071 |     var extractedTags = job.extractedTags;
    1072 |     var decorations = job.decorations;
    1073 | 
    1074 |     var html = [];
    1075 |     // index past the last char in sourceText written to html
    1076 |     var outputIdx = 0;
    1077 | 
    1078 |     var openDecoration = null;
    1079 |     var currentDecoration = null;
    1080 |     var tagPos = 0;  // index into extractedTags
    1081 |     var decPos = 0;  // index into decorations
    1082 |     var tabExpander = makeTabExpander(window['PR_TAB_WIDTH']);
    1083 | 
    1084 |     var adjacentSpaceRe = /([\r\n ]) /g;
    1085 |     var startOrSpaceRe = /(^| ) /gm;
    1086 |     var newlineRe = /\r\n?|\n/g;
    1087 |     var trailingSpaceRe = /[ \r\n]$/;
    1088 |     var lastWasSpace = true;  // the last text chunk emitted ended with a space.
    1089 | 
    1090 |     // See bug 71 and http://stackoverflow.com/questions/136443/why-doesnt-ie7-
    1091 |     var isIE678 = window['_pr_isIE6']();
    1092 |     var lineBreakHtml = (
    1093 |         isIE678
    1094 |         ? (job.sourceNode.tagName === 'PRE'
    1095 |            // Use line feeds instead of <br>s so that copying and pasting works
    1096 |            // on IE.
    1097 |            // Doing this on other browsers breaks lots of stuff since \r\n is
    1098 |            // treated as two newlines on Firefox.
    1099 |            ? (isIE678 === 6 ? '&#160;\r\n' :
    1100 |               isIE678 === 7 ? '&#160;<br>\r' : '&#160;\r')
    1101 |            // IE collapses multiple adjacent <br>s into 1 line break.
    1102 |            // Prefix every newline with '&#160;' to prevent such behavior.
    1103 |            // &nbsp; is the same as &#160; but works in XML as well as HTML.
    1104 |            : '&#160;<br />')
    1105 |         : '<br />');
    1106 | 
    1107 |     // Look for a class like linenums or linenums:<n> where <n> is the 1-indexed
    1108 |     // number of the first line.
    1109 |     var numberLines = job.sourceNode.className.match(/\blinenums\b(?::(\d+))?/);
    1110 |     var lineBreaker;
    1111 |     if (numberLines) {
    1112 |       var lineBreaks = [];
    1113 |       for (var i = 0; i < 10; ++i) {
    1114 |         lineBreaks[i] = lineBreakHtml + '</li><li class="L' + i + '">';
    1115 |       }
    1116 |       var lineNum = numberLines[1] && numberLines[1].length 
    1117 |           ? numberLines[1] - 1 : 0;  // Lines are 1-indexed
    1118 |       html.push('<ol class="linenums"><li class="L', (lineNum) % 10, '"');
    1119 |       if (lineNum) {
    1120 |         html.push(' value="', lineNum + 1, '"');
    1121 |       }
    1122 |       html.push('>');
    1123 |       lineBreaker = function () {
    1124 |         var lb = lineBreaks[++lineNum % 10];
    1125 |         // If a decoration is open, we need to close it before closing a list-item
    1126 |         // and reopen it on the other side of the list item.
    1127 |         return openDecoration
    1128 |             ? ('</span>' + lb + '<span class="' + openDecoration + '">') : lb;
    1129 |       };
    1130 |     } else {
    1131 |       lineBreaker = lineBreakHtml;
    1132 |     }
    1133 | 
    1134 |     // A helper function that is responsible for opening sections of decoration
    1135 |     // and outputing properly escaped chunks of source
    1136 |     function emitTextUpTo(sourceIdx) {
    1137 |       if (sourceIdx > outputIdx) {
    1138 |         if (openDecoration && openDecoration !== currentDecoration) {
    1139 |           // Close the current decoration
    1140 |           html.push('</span>');
    1141 |           openDecoration = null;
    1142 |         }
    1143 |         if (!openDecoration && currentDecoration) {
    1144 |           openDecoration = currentDecoration;
    1145 |           html.push('<span class="', openDecoration, '">');
    1146 |         }
    1147 |         // This interacts badly with some wikis which introduces paragraph tags
    1148 |         // into pre blocks for some strange reason.
    1149 |         // It's necessary for IE though which seems to lose the preformattedness
    1150 |         // of <pre> tags when their innerHTML is assigned.
    1151 |         // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
    1152 |         // and it serves to undo the conversion of <br>s to newlines done in
    1153 |         // chunkify.
    1154 |         var htmlChunk = textToHtml(
    1155 |             tabExpander(sourceText.substring(outputIdx, sourceIdx)))
    1156 |             .replace(lastWasSpace
    1157 |                      ? startOrSpaceRe
    1158 |                      : adjacentSpaceRe, '$1&#160;');
    1159 |         // Keep track of whether we need to escape space at the beginning of the
    1160 |         // next chunk.
    1161 |         lastWasSpace = trailingSpaceRe.test(htmlChunk);
    1162 |         html.push(htmlChunk.replace(newlineRe, lineBreaker));
    1163 |         outputIdx = sourceIdx;
    1164 |       }
    1165 |     }
    1166 | 
    1167 |     while (true) {
    1168 |       // Determine if we're going to consume a tag this time around.  Otherwise
    1169 |       // we consume a decoration or exit.
    1170 |       var outputTag;
    1171 |       if (tagPos < extractedTags.length) {
    1172 |         if (decPos < decorations.length) {
    1173 |           // Pick one giving preference to extractedTags since we shouldn't open
    1174 |           // a new style that we're going to have to immediately close in order
    1175 |           // to output a tag.
    1176 |           outputTag = extractedTags[tagPos] <= decorations[decPos];
    1177 |         } else {
    1178 |           outputTag = true;
    1179 |         }
    1180 |       } else {
    1181 |         outputTag = false;
    1182 |       }
    1183 |       // Consume either a decoration or a tag or exit.
    1184 |       if (outputTag) {
    1185 |         emitTextUpTo(extractedTags[tagPos]);
    1186 |         if (openDecoration) {
    1187 |           // Close the current decoration
    1188 |           html.push('</span>');
    1189 |           openDecoration = null;
    1190 |         }
    1191 |         html.push(extractedTags[tagPos + 1]);
    1192 |         tagPos += 2;
    1193 |       } else if (decPos < decorations.length) {
    1194 |         emitTextUpTo(decorations[decPos]);
    1195 |         currentDecoration = decorations[decPos + 1];
    1196 |         decPos += 2;
    1197 |       } else {
    1198 |         break;
    1199 |       }
    1200 |     }
    1201 |     emitTextUpTo(sourceText.length);
    1202 |     if (openDecoration) {
    1203 |       html.push('</span>');
    1204 |     }
    1205 |     if (numberLines) { html.push('</li></ol>'); }
    1206 |     job.prettyPrintedHtml = html.join('');
    1207 |   }
    1208 | 
    1209 |   /** Maps language-specific file extensions to handlers. */
    1210 |   var langHandlerRegistry = {};
    1211 |   /** Register a language handler for the given file extensions.
    1212 |     * @param {function (Object)} handler a function from source code to a list
    1213 |     *      of decorations.  Takes a single argument job which describes the
    1214 |     *      state of the computation.   The single parameter has the form
    1215 |     *      {@code {
    1216 |     *        source: {string} as plain text.
    1217 |     *        decorations: {Array.<number|string>} an array of style classes
    1218 |     *                     preceded by the position at which they start in
    1219 |     *                     job.source in order.
    1220 |     *                     The language handler should assigned this field.
    1221 |     *        basePos: {int} the position of source in the larger source chunk.
    1222 |     *                 All positions in the output decorations array are relative
    1223 |     *                 to the larger source chunk.
    1224 |     *      } }
    1225 |     * @param {Array.<string>} fileExtensions
    1226 |     */
    1227 |   function registerLangHandler(handler, fileExtensions) {
    1228 |     for (var i = fileExtensions.length; --i >= 0;) {
    1229 |       var ext = fileExtensions[i];
    1230 |       if (!langHandlerRegistry.hasOwnProperty(ext)) {
    1231 |         langHandlerRegistry[ext] = handler;
    1232 |       } else if ('console' in window) {
    1233 |         console['warn']('cannot override language handler %s', ext);
    1234 |       }
    1235 |     }
    1236 |   }
    1237 |   function langHandlerForExtension(extension, source) {
    1238 |     if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
    1239 |       // Treat it as markup if the first non whitespace character is a < and
    1240 |       // the last non-whitespace character is a >.
    1241 |       extension = /^\s*</.test(source)
    1242 |           ? 'default-markup'
    1243 |           : 'default-code';
    1244 |     }
    1245 |     return langHandlerRegistry[extension];
    1246 |   }
    1247 |   registerLangHandler(decorateSource, ['default-code']);
    1248 |   registerLangHandler(
    1249 |       createSimpleLexer(
    1250 |           [],
    1251 |           [
    1252 |            [PR_PLAIN,       /^[^<?]+/],
    1253 |            [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
    1254 |            [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
    1255 |            // Unescaped content in an unknown language
    1256 |            ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
    1257 |            ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
    1258 |            [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
    1259 |            ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
    1260 |            // Unescaped content in javascript.  (Or possibly vbscript).
    1261 |            ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
    1262 |            // Contains unescaped stylesheet content
    1263 |            ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
    1264 |            ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
    1265 |           ]),
    1266 |       ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
    1267 |   registerLangHandler(
    1268 |       createSimpleLexer(
    1269 |           [
    1270 |            [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
    1271 |            [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
    1272 |            ],
    1273 |           [
    1274 |            [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
    1275 |            [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
    1276 |            ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
    1277 |            [PR_PUNCTUATION,  /^[=<>\/]+/],
    1278 |            ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
    1279 |            ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
    1280 |            ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
    1281 |            ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
    1282 |            ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
    1283 |            ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
    1284 |            ]),
    1285 |       ['in.tag']);
    1286 |   registerLangHandler(
    1287 |       createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
    1288 |   registerLangHandler(sourceDecorator({
    1289 |           'keywords': CPP_KEYWORDS,
    1290 |           'hashComments': true,
    1291 |           'cStyleComments': true
    1292 |         }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
    1293 |   registerLangHandler(sourceDecorator({
    1294 |           'keywords': 'null true false'
    1295 |         }), ['json']);
    1296 |   registerLangHandler(sourceDecorator({
    1297 |           'keywords': CSHARP_KEYWORDS,
    1298 |           'hashComments': true,
    1299 |           'cStyleComments': true,
    1300 |           'verbatimStrings': true
    1301 |         }), ['cs']);
    1302 |   registerLangHandler(sourceDecorator({
    1303 |           'keywords': JAVA_KEYWORDS,
    1304 |           'cStyleComments': true
    1305 |         }), ['java']);
    1306 |   registerLangHandler(sourceDecorator({
    1307 |           'keywords': SH_KEYWORDS,
    1308 |           'hashComments': true,
    1309 |           'multiLineStrings': true
    1310 |         }), ['bsh', 'csh', 'sh']);
    1311 |   registerLangHandler(sourceDecorator({
    1312 |           'keywords': PYTHON_KEYWORDS,
    1313 |           'hashComments': true,
    1314 |           'multiLineStrings': true,
    1315 |           'tripleQuotedStrings': true
    1316 |         }), ['cv', 'py']);
    1317 |   registerLangHandler(sourceDecorator({
    1318 |           'keywords': PERL_KEYWORDS,
    1319 |           'hashComments': true,
    1320 |           'multiLineStrings': true,
    1321 |           'regexLiterals': true
    1322 |         }), ['perl', 'pl', 'pm']);
    1323 |   registerLangHandler(sourceDecorator({
    1324 |           'keywords': RUBY_KEYWORDS,
    1325 |           'hashComments': true,
    1326 |           'multiLineStrings': true,
    1327 |           'regexLiterals': true
    1328 |         }), ['rb']);
    1329 |   registerLangHandler(sourceDecorator({
    1330 |           'keywords': JSCRIPT_KEYWORDS,
    1331 |           'cStyleComments': true,
    1332 |           'regexLiterals': true
    1333 |         }), ['js']);
    1334 |   registerLangHandler(
    1335 |       createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
    1336 | 
    1337 |   function applyDecorator(job) {
    1338 |     var sourceCodeHtml = job.sourceCodeHtml;
    1339 |     var opt_langExtension = job.langExtension;
    1340 | 
    1341 |     // Prepopulate output in case processing fails with an exception.
    1342 |     job.prettyPrintedHtml = sourceCodeHtml;
    1343 | 
    1344 |     try {
    1345 |       // Extract tags, and convert the source code to plain text.
    1346 |       var sourceAndExtractedTags = extractTags(sourceCodeHtml);
    1347 |       /** Plain text. @type {string} */
    1348 |       var source = sourceAndExtractedTags.source;
    1349 |       job.source = source;
    1350 |       job.basePos = 0;
    1351 | 
    1352 |       /** Even entries are positions in source in ascending order.  Odd entries
    1353 |         * are tags that were extracted at that position.
    1354 |         * @type {Array.<number|string>}
    1355 |         */
    1356 |       job.extractedTags = sourceAndExtractedTags.tags;
    1357 | 
    1358 |       // Apply the appropriate language handler
    1359 |       langHandlerForExtension(opt_langExtension, source)(job);
    1360 |       // Integrate the decorations and tags back into the source code to produce
    1361 |       // a decorated html string which is left in job.prettyPrintedHtml.
    1362 |       recombineTagsAndDecorations(job);
    1363 |     } catch (e) {
    1364 |       if ('console' in window) {
    1365 |         console['log'](e && e['stack'] ? e['stack'] : e);
    1366 |       }
    1367 |     }
    1368 |   }
    1369 | 
    1370 |   function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
    1371 |     var job = {
    1372 |       sourceCodeHtml: sourceCodeHtml,
    1373 |       langExtension: opt_langExtension
    1374 |     };
    1375 |     applyDecorator(job);
    1376 |     return job.prettyPrintedHtml;
    1377 |   }
    1378 | 
    1379 |   function prettyPrint(opt_whenDone) {
    1380 |     // fetch a list of nodes to rewrite
    1381 |     var codeSegments = [
    1382 |         document.getElementsByTagName('pre'),
    1383 |         document.getElementsByTagName('code'),
    1384 |         document.getElementsByTagName('xmp') ];
    1385 |     var elements = [];
    1386 |     for (var i = 0; i < codeSegments.length; ++i) {
    1387 |       for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
    1388 |         elements.push(codeSegments[i][j]);
    1389 |       }
    1390 |     }
    1391 |     codeSegments = null;
    1392 | 
    1393 |     var clock = Date;
    1394 |     if (!clock['now']) {
    1395 |       clock = { 'now': function () { return (new Date).getTime(); } };
    1396 |     }
    1397 | 
    1398 |     // The loop is broken into a series of continuations to make sure that we
    1399 |     // don't make the browser unresponsive when rewriting a large page.
    1400 |     var k = 0;
    1401 |     var prettyPrintingJob;
    1402 | 
    1403 |     function doWork() {
    1404 |       var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
    1405 |                      clock.now() + 250 /* ms */ :
    1406 |                      Infinity);
    1407 |       for (; k < elements.length && clock.now() < endTime; k++) {
    1408 |         var cs = elements[k];
    1409 |         if (cs.className && cs.className.indexOf('prettyprint') >= 0) {
    1410 |           // If the classes includes a language extensions, use it.
    1411 |           // Language extensions can be specified like
    1412 |           //     <pre class="prettyprint lang-cpp">
    1413 |           // the language extension "cpp" is used to find a language handler as
    1414 |           // passed to PR_registerLangHandler.
    1415 |           var langExtension = cs.className.match(/\blang-(\w+)\b/);
    1416 |           if (langExtension) { langExtension = langExtension[1]; }
    1417 | 
    1418 |           // make sure this is not nested in an already prettified element
    1419 |           var nested = false;
    1420 |           for (var p = cs.parentNode; p; p = p.parentNode) {
    1421 |             if ((p.tagName === 'pre' || p.tagName === 'code' ||
    1422 |                  p.tagName === 'xmp') &&
    1423 |                 p.className && p.className.indexOf('prettyprint') >= 0) {
    1424 |               nested = true;
    1425 |               break;
    1426 |             }
    1427 |           }
    1428 |           if (!nested) {
    1429 |             // fetch the content as a snippet of properly escaped HTML.
    1430 |             // Firefox adds newlines at the end.
    1431 |             var content = getInnerHtml(cs);
    1432 |             content = content.replace(/(?:\r\n?|\n)$/, '');
    1433 | 
    1434 |             // do the pretty printing
    1435 |             prettyPrintingJob = {
    1436 |               sourceCodeHtml: content,
    1437 |               langExtension: langExtension,
    1438 |               sourceNode: cs
    1439 |             };
    1440 |             applyDecorator(prettyPrintingJob);
    1441 |             replaceWithPrettyPrintedHtml();
    1442 |           }
    1443 |         }
    1444 |       }
    1445 |       if (k < elements.length) {
    1446 |         // finish up in a continuation
    1447 |         setTimeout(doWork, 250);
    1448 |       } else if (opt_whenDone) {
    1449 |         opt_whenDone();
    1450 |       }
    1451 |     }
    1452 | 
    1453 |     function replaceWithPrettyPrintedHtml() {
    1454 |       var newContent = prettyPrintingJob.prettyPrintedHtml;
    1455 |       if (!newContent) { return; }
    1456 |       var cs = prettyPrintingJob.sourceNode;
    1457 | 
    1458 |       // push the prettified html back into the tag.
    1459 |       if (!isRawContent(cs)) {
    1460 |         // just replace the old html with the new
    1461 |         cs.innerHTML = newContent;
    1462 |       } else {
    1463 |         // we need to change the tag to a <pre> since <xmp>s do not allow
    1464 |         // embedded tags such as the span tags used to attach styles to
    1465 |         // sections of source code.
    1466 |         var pre = document.createElement('PRE');
    1467 |         for (var i = 0; i < cs.attributes.length; ++i) {
    1468 |           var a = cs.attributes[i];
    1469 |           if (a.specified) {
    1470 |             var aname = a.name.toLowerCase();
    1471 |             if (aname === 'class') {
    1472 |               pre.className = a.value;  // For IE 6
    1473 |             } else {
    1474 |               pre.setAttribute(a.name, a.value);
    1475 |             }
    1476 |           }
    1477 |         }
    1478 |         pre.innerHTML = newContent;
    1479 | 
    1480 |         // remove the old
    1481 |         cs.parentNode.replaceChild(pre, cs);
    1482 |         cs = pre;
    1483 |       }
    1484 |     }
    1485 | 
    1486 |     doWork();
    1487 |   }
    1488 | 
    1489 |   window['PR_normalizedHtml'] = normalizedHtml;
    1490 |   window['prettyPrintOne'] = prettyPrintOne;
    1491 |   window['prettyPrint'] = prettyPrint;
    1492 |   window['PR'] = {
    1493 |         'combinePrefixPatterns': combinePrefixPatterns,
    1494 |         'createSimpleLexer': createSimpleLexer,
    1495 |         'registerLangHandler': registerLangHandler,
    1496 |         'sourceDecorator': sourceDecorator,
    1497 |         'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
    1498 |         'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
    1499 |         'PR_COMMENT': PR_COMMENT,
    1500 |         'PR_DECLARATION': PR_DECLARATION,
    1501 |         'PR_KEYWORD': PR_KEYWORD,
    1502 |         'PR_LITERAL': PR_LITERAL,
    1503 |         'PR_NOCODE': PR_NOCODE,
    1504 |         'PR_PLAIN': PR_PLAIN,
    1505 |         'PR_PUNCTUATION': PR_PUNCTUATION,
    1506 |         'PR_SOURCE': PR_SOURCE,
    1507 |         'PR_STRING': PR_STRING,
    1508 |         'PR_TAG': PR_TAG,
    1509 |         'PR_TYPE': PR_TYPE
    1510 |       };
    1511 | })();
    1512 | 
    
    
    --------------------------------------------------------------------------------