├── .gitignore ├── chrome.manifest ├── chrome ├── content │ ├── codemirror.js │ ├── codemirror │ │ ├── LICENSE │ │ ├── parsecss.js │ │ ├── parsehtmlmixed.js │ │ ├── parsejavascript.js │ │ ├── parsexml.js │ │ ├── stringstream.js │ │ ├── tokenize.js │ │ ├── tokenizejavascript.js │ │ └── util.js │ ├── firerainbow.js │ ├── firerainbow.xul │ ├── import.xul │ └── worker.js └── skin │ ├── import.css │ ├── rainbow.css │ └── rainbow.png ├── defaults └── preferences │ └── firerainbow.js ├── install.rdf ├── license.txt ├── rakefile ├── readme.md ├── support ├── example.html └── screenshot.png ├── themes ├── active4d.css ├── all_hallows_eve.css ├── amy.css ├── blackboard.css ├── brilliance_black.css ├── brilliance_dull.css ├── cobalt.css ├── codemirror.css ├── cowtown.css ├── dawn.css ├── eclipse.css ├── eiffel.css ├── espresso_libre.css ├── idle.css ├── iplastic.css ├── ir_black.css ├── lazy.css ├── mac_classic.css ├── magicwb_amiga.css ├── pastels_on_dark.css ├── rainbow.css ├── slush_poppies.css ├── spacecadet.css ├── sunburst.css ├── twilight.css └── zenburnesque.css └── utils └── Rakefile /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | _layouts 3 | shared 4 | build 5 | tmp 6 | .DS_Store 7 | Thumbs.db 8 | _site -------------------------------------------------------------------------------- /chrome.manifest: -------------------------------------------------------------------------------- 1 | content firerainbow chrome/content/ 2 | skin firerainbow classic/1.0 chrome/skin/ 3 | locale firerainbow en-US chrome/locale/en-US/ 4 | 5 | overlay chrome://firebug/content/firebugOverlay.xul chrome://firerainbow/content/firerainbow.xul -------------------------------------------------------------------------------- /chrome/content/codemirror.js: -------------------------------------------------------------------------------- 1 | // !!! DO NOT EDIT THIS FILE (GENERATED) !!! 2 | // this file was generated from codemirror subdirectory by `rake sandbox` task 3 | 4 | 5 | // Copyright (c) 2007-2010 Marijn Haverbeke 6 | // 7 | // This software is provided 'as-is', without any express or implied 8 | // warranty. In no event will the authors be held liable for any 9 | // damages arising from the use of this software. 10 | // 11 | // Permission is granted to anyone to use this software for any 12 | // purpose, including commercial applications, and to alter it and 13 | // redistribute it freely, subject to the following restrictions: 14 | // 15 | // 1. The origin of this software must not be misrepresented; you must 16 | // not claim that you wrote the original software. If you use this 17 | // software in a product, an acknowledgment in the product 18 | // documentation would be appreciated but is not required. 19 | // 20 | // 2. Altered source versions must be plainly marked as such, and must 21 | // not be misrepresented as being the original software. 22 | // 23 | // 3. This notice may not be removed or altered from any source 24 | // distribution. 25 | // 26 | // Marijn Haverbeke 27 | // marijnh@gmail.com 28 | 29 | 30 | var codemirror = (function() { 31 | var Editor = {};var indentUnit = 2;var window=this; 32 | /* A few useful utility functions. */ 33 | 34 | // Capture a method on an object. 35 | function method(obj, name) { 36 | return function() {obj[name].apply(obj, arguments);}; 37 | } 38 | 39 | // The value used to signal the end of a sequence in iterators. 40 | this.StopIteration = {toString: function() {return "StopIteration"}}; 41 | 42 | // Apply a function to each element in a sequence. 43 | this.forEach = function(iter, f) { 44 | if (iter.next) { 45 | try {while (true) f(iter.next());} 46 | catch (e) {if (e != StopIteration) throw e;} 47 | } 48 | else { 49 | for (var i = 0; i < iter.length; i++) 50 | f(iter[i]); 51 | } 52 | } 53 | 54 | // Map a function over a sequence, producing an array of results. 55 | function map(iter, f) { 56 | var accum = []; 57 | forEach(iter, function(val) {accum.push(f(val));}); 58 | return accum; 59 | } 60 | 61 | // Create a predicate function that tests a string againsts a given 62 | // regular expression. No longer used but might be used by 3rd party 63 | // parsers. 64 | function matcher(regexp){ 65 | return function(value){return regexp.test(value);}; 66 | } 67 | 68 | // Test whether a DOM node has a certain CSS class. 69 | function hasClass(element, className) { 70 | var classes = element.className; 71 | return classes && new RegExp("(^| )" + className + "($| )").test(classes); 72 | } 73 | function removeClass(element, className) { 74 | element.className = element.className.replace(new RegExp(" " + className + "\\b", "g"), ""); 75 | return element; 76 | } 77 | 78 | // Insert a DOM node after another node. 79 | function insertAfter(newNode, oldNode) { 80 | var parent = oldNode.parentNode; 81 | parent.insertBefore(newNode, oldNode.nextSibling); 82 | return newNode; 83 | } 84 | 85 | function removeElement(node) { 86 | if (node.parentNode) 87 | node.parentNode.removeChild(node); 88 | } 89 | 90 | function clearElement(node) { 91 | while (node.firstChild) 92 | node.removeChild(node.firstChild); 93 | } 94 | 95 | // Check whether a node is contained in another one. 96 | function isAncestor(node, child) { 97 | while (child = child.parentNode) { 98 | if (node == child) 99 | return true; 100 | } 101 | return false; 102 | } 103 | 104 | // The non-breaking space character. 105 | var nbsp = "\u00a0"; 106 | var matching = {"{": "}", "[": "]", "(": ")", 107 | "}": "{", "]": "[", ")": "("}; 108 | 109 | // Standardize a few unportable event properties. 110 | function normalizeEvent(event) { 111 | if (!event.stopPropagation) { 112 | event.stopPropagation = function() {this.cancelBubble = true;}; 113 | event.preventDefault = function() {this.returnValue = false;}; 114 | } 115 | if (!event.stop) { 116 | event.stop = function() { 117 | this.stopPropagation(); 118 | this.preventDefault(); 119 | }; 120 | } 121 | 122 | if (event.type == "keypress") { 123 | event.code = (event.charCode == null) ? event.keyCode : event.charCode; 124 | event.character = String.fromCharCode(event.code); 125 | } 126 | return event; 127 | } 128 | 129 | // Portably register event handlers. 130 | function addEventHandler(node, type, handler, removeFunc) { 131 | function wrapHandler(event) { 132 | handler(normalizeEvent(event || window.event)); 133 | } 134 | if (typeof node.addEventListener == "function") { 135 | node.addEventListener(type, wrapHandler, false); 136 | if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);}; 137 | } 138 | else { 139 | node.attachEvent("on" + type, wrapHandler); 140 | if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);}; 141 | } 142 | } 143 | 144 | function nodeText(node) { 145 | return node.textContent || node.innerText || node.nodeValue || ""; 146 | } 147 | 148 | function nodeTop(node) { 149 | var top = 0; 150 | while (node.offsetParent) { 151 | top += node.offsetTop; 152 | node = node.offsetParent; 153 | } 154 | return top; 155 | } 156 | 157 | function isBR(node) { 158 | var nn = node.nodeName; 159 | return nn == "BR" || nn == "br"; 160 | } 161 | function isSpan(node) { 162 | var nn = node.nodeName; 163 | return nn == "SPAN" || nn == "span"; 164 | } 165 | 166 | // A framework for simple tokenizers. Takes care of newlines and 167 | // white-space, and of getting the text from the source stream into 168 | // the token object. A state is a function of two arguments -- a 169 | // string stream and a setState function. The second can be used to 170 | // change the tokenizer's state, and can be ignored for stateless 171 | // tokenizers. This function should advance the stream over a token 172 | // and return a string or object containing information about the next 173 | // token, or null to pass and have the (new) state be called to finish 174 | // the token. When a string is given, it is wrapped in a {style, type} 175 | // object. In the resulting object, the characters consumed are stored 176 | // under the content property. Any whitespace following them is also 177 | // automatically consumed, and added to the value property. (Thus, 178 | // content is the actual meaningful part of the token, while value 179 | // contains all the text it spans.) 180 | 181 | function tokenizer(source, state) { 182 | // Newlines are always a separate token. 183 | function isWhiteSpace(ch) { 184 | // The messy regexp is because IE's regexp matcher is of the 185 | // opinion that non-breaking spaces are no whitespace. 186 | return ch != "\n" && /^[\s\u00a0]*$/.test(ch); 187 | } 188 | 189 | var tokenizer = { 190 | state: state, 191 | 192 | take: function(type) { 193 | if (typeof(type) == "string") 194 | type = {style: type, type: type}; 195 | 196 | type.content = (type.content || "") + source.get(); 197 | if (!/\n$/.test(type.content)) 198 | source.nextWhile(isWhiteSpace); 199 | type.value = type.content + source.get(); 200 | return type; 201 | }, 202 | 203 | next: function () { 204 | if (!source.more()) throw StopIteration; 205 | 206 | var type; 207 | if (source.equals("\n")) { 208 | source.next(); 209 | return this.take("whitespace"); 210 | } 211 | 212 | if (source.applies(isWhiteSpace)) 213 | type = "whitespace"; 214 | else 215 | while (!type) 216 | type = this.state(source, function(s) {tokenizer.state = s;}); 217 | 218 | return this.take(type); 219 | } 220 | }; 221 | return tokenizer; 222 | } 223 | 224 | /* Tokenizer for JavaScript code */ 225 | 226 | var tokenizeJavaScript = (function() { 227 | // Advance the stream until the given character (not preceded by a 228 | // backslash) is encountered, or the end of the line is reached. 229 | function nextUntilUnescaped(source, end) { 230 | var escaped = false; 231 | while (!source.endOfLine()) { 232 | var next = source.next(); 233 | if (next == end && !escaped) 234 | return false; 235 | escaped = !escaped && next == "\\"; 236 | } 237 | return escaped; 238 | } 239 | 240 | // A map of JavaScript's keywords. The a/b/c keyword distinction is 241 | // very rough, but it gives the parser enough information to parse 242 | // correct code correctly (we don't care that much how we parse 243 | // incorrect code). The style information included in these objects 244 | // is used by the highlighter to pick the correct CSS style for a 245 | // token. 246 | var keywords = function(){ 247 | function result(type, style){ 248 | return {type: type, style: "js-" + style}; 249 | } 250 | // keywords that take a parenthised expression, and then a 251 | // statement (if) 252 | var keywordA = result("keyword a", "keyword"); 253 | // keywords that take just a statement (else) 254 | var keywordB = result("keyword b", "keyword"); 255 | // keywords that optionally take an expression, and form a 256 | // statement (return) 257 | var keywordC = result("keyword c", "keyword"); 258 | var operator = result("operator", "keyword"); 259 | var atom = result("atom", "atom"); 260 | return { 261 | "if": keywordA, "while": keywordA, "with": keywordA, 262 | "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB, 263 | "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC, 264 | "in": operator, "typeof": operator, "instanceof": operator, 265 | "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"), 266 | "for": result("for", "keyword"), "switch": result("switch", "keyword"), 267 | "case": result("case", "keyword"), "default": result("default", "keyword"), 268 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 269 | }; 270 | }(); 271 | 272 | // Some helper regexps 273 | var isOperatorChar = /[+\-*&%=<>!?|]/; 274 | var isHexDigit = /[0-9A-Fa-f]/; 275 | var isWordChar = /[\w\$_]/; 276 | 277 | // Wrapper around jsToken that helps maintain parser state (whether 278 | // we are inside of a multi-line comment and whether the next token 279 | // could be a regular expression). 280 | function jsTokenState(inside, regexp) { 281 | return function(source, setState) { 282 | var newInside = inside; 283 | var type = jsToken(inside, regexp, source, function(c) {newInside = c;}); 284 | var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/); 285 | if (newRegexp != regexp || newInside != inside) 286 | setState(jsTokenState(newInside, newRegexp)); 287 | return type; 288 | }; 289 | } 290 | 291 | // The token reader, intended to be used by the tokenizer from 292 | // tokenize.js (through jsTokenState). Advances the source stream 293 | // over a token, and returns an object containing the type and style 294 | // of that token. 295 | function jsToken(inside, regexp, source, setInside) { 296 | function readHexNumber(){ 297 | source.next(); // skip the 'x' 298 | source.nextWhileMatches(isHexDigit); 299 | return {type: "number", style: "js-atom"}; 300 | } 301 | 302 | function readNumber() { 303 | source.nextWhileMatches(/[0-9]/); 304 | if (source.equals(".")){ 305 | source.next(); 306 | source.nextWhileMatches(/[0-9]/); 307 | } 308 | if (source.equals("e") || source.equals("E")){ 309 | source.next(); 310 | if (source.equals("-")) 311 | source.next(); 312 | source.nextWhileMatches(/[0-9]/); 313 | } 314 | return {type: "number", style: "js-atom"}; 315 | } 316 | // Read a word, look it up in keywords. If not found, it is a 317 | // variable, otherwise it is a keyword of the type found. 318 | function readWord() { 319 | source.nextWhileMatches(isWordChar); 320 | var word = source.get(); 321 | var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word]; 322 | return known ? {type: known.type, style: known.style, content: word} : 323 | {type: "variable", style: "js-variable", content: word}; 324 | } 325 | function readRegexp() { 326 | nextUntilUnescaped(source, "/"); 327 | source.nextWhileMatches(/[gimy]/); // 'y' is "sticky" option in Mozilla 328 | return {type: "regexp", style: "js-string"}; 329 | } 330 | // Mutli-line comments are tricky. We want to return the newlines 331 | // embedded in them as regular newline tokens, and then continue 332 | // returning a comment token for every line of the comment. So 333 | // some state has to be saved (inside) to indicate whether we are 334 | // inside a /* */ sequence. 335 | function readMultilineComment(start){ 336 | var newInside = "/*"; 337 | var maybeEnd = (start == "*"); 338 | while (true) { 339 | if (source.endOfLine()) 340 | break; 341 | var next = source.next(); 342 | if (next == "/" && maybeEnd){ 343 | newInside = null; 344 | break; 345 | } 346 | maybeEnd = (next == "*"); 347 | } 348 | setInside(newInside); 349 | return {type: "comment", style: "js-comment"}; 350 | } 351 | function readOperator() { 352 | source.nextWhileMatches(isOperatorChar); 353 | return {type: "operator", style: "js-operator"}; 354 | } 355 | function readString(quote) { 356 | var endBackSlash = nextUntilUnescaped(source, quote); 357 | setInside(endBackSlash ? quote : null); 358 | return {type: "string", style: "js-string"}; 359 | } 360 | 361 | // Fetch the next token. Dispatches on first character in the 362 | // stream, or first two characters when the first is a slash. 363 | if (inside == "\"" || inside == "'") 364 | return readString(inside); 365 | var ch = source.next(); 366 | if (inside == "/*") 367 | return readMultilineComment(ch); 368 | else if (ch == "\"" || ch == "'") 369 | return readString(ch); 370 | // with punctuation, the type of the token is the symbol itself 371 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 372 | return {type: ch, style: "js-punctuation"}; 373 | else if (ch == "0" && (source.equals("x") || source.equals("X"))) 374 | return readHexNumber(); 375 | else if (/[0-9]/.test(ch)) 376 | return readNumber(); 377 | else if (ch == "/"){ 378 | if (source.equals("*")) 379 | { source.next(); return readMultilineComment(ch); } 380 | else if (source.equals("/")) 381 | { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};} 382 | else if (regexp) 383 | return readRegexp(); 384 | else 385 | return readOperator(); 386 | } 387 | else if (isOperatorChar.test(ch)) 388 | return readOperator(); 389 | else 390 | return readWord(); 391 | } 392 | 393 | // The external interface to the tokenizer. 394 | return function(source, startState) { 395 | return tokenizer(source, startState || jsTokenState(false, true)); 396 | }; 397 | })(); 398 | 399 | /* Parse function for JavaScript. Makes use of the tokenizer from 400 | * tokenizejavascript.js. Note that your parsers do not have to be 401 | * this complicated -- if you don't want to recognize local variables, 402 | * in many languages it is enough to just look for braces, semicolons, 403 | * parentheses, etc, and know when you are inside a string or comment. 404 | * 405 | * See manual.html for more info about the parser interface. 406 | */ 407 | 408 | this.JSParser = Editor.Parser = (function() { 409 | // Token types that can be considered to be atoms. 410 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; 411 | // Setting that can be used to have JSON data indent properly. 412 | var json = false; 413 | // Constructor for the lexical context objects. 414 | function JSLexical(indented, column, type, align, prev, info) { 415 | // indentation at start of this line 416 | this.indented = indented; 417 | // column at which this scope was opened 418 | this.column = column; 419 | // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(') 420 | this.type = type; 421 | // '[', '{', or '(' blocks that have any text after their opening 422 | // character are said to be 'aligned' -- any lines below are 423 | // indented all the way to the opening character. 424 | if (align != null) 425 | this.align = align; 426 | // Parent scope, if any. 427 | this.prev = prev; 428 | this.info = info; 429 | } 430 | 431 | // My favourite JavaScript indentation rules. 432 | function indentJS(lexical) { 433 | return function(firstChars) { 434 | var firstChar = firstChars && firstChars.charAt(0), type = lexical.type; 435 | var closing = firstChar == type; 436 | if (type == "vardef") 437 | return lexical.indented + 4; 438 | else if (type == "form" && firstChar == "{") 439 | return lexical.indented; 440 | else if (type == "stat" || type == "form") 441 | return lexical.indented + indentUnit; 442 | else if (lexical.info == "switch" && !closing) 443 | return lexical.indented + (/^(?:case|default)\b/.test(firstChars) ? indentUnit : 2 * indentUnit); 444 | else if (lexical.align) 445 | return lexical.column - (closing ? 1 : 0); 446 | else 447 | return lexical.indented + (closing ? 0 : indentUnit); 448 | }; 449 | } 450 | 451 | // The parser-iterator-producing function itself. 452 | function parseJS(input, basecolumn) { 453 | // Wrap the input in a token stream 454 | var tokens = tokenizeJavaScript(input); 455 | // The parser state. cc is a stack of actions that have to be 456 | // performed to finish the current statement. For example we might 457 | // know that we still need to find a closing parenthesis and a 458 | // semicolon. Actions at the end of the stack go first. It is 459 | // initialized with an infinitely looping action that consumes 460 | // whole statements. 461 | var cc = [json ? expressions : statements]; 462 | // Context contains information about the current local scope, the 463 | // variables defined in that, and the scopes above it. 464 | var context = null; 465 | // The lexical scope, used mostly for indentation. 466 | var lexical = new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false); 467 | // Current column, and the indentation at the start of the current 468 | // line. Used to create lexical scope objects. 469 | var column = 0; 470 | var indented = 0; 471 | // Variables which are used by the mark, cont, and pass functions 472 | // below to communicate with the driver loop in the 'next' 473 | // function. 474 | var consume, marked; 475 | 476 | // The iterator object. 477 | var parser = {next: next, copy: copy}; 478 | 479 | function next(){ 480 | // Start by performing any 'lexical' actions (adjusting the 481 | // lexical variable), or the operations below will be working 482 | // with the wrong lexical state. 483 | while(cc[cc.length - 1].lex) 484 | cc.pop()(); 485 | 486 | // Fetch a token. 487 | var token = tokens.next(); 488 | 489 | // Adjust column and indented. 490 | if (token.type == "whitespace" && column == 0) 491 | indented = token.value.length; 492 | column += token.value.length; 493 | if (token.content == "\n"){ 494 | indented = column = 0; 495 | // If the lexical scope's align property is still undefined at 496 | // the end of the line, it is an un-aligned scope. 497 | if (!("align" in lexical)) 498 | lexical.align = false; 499 | // Newline tokens get an indentation function associated with 500 | // them. 501 | token.indentation = indentJS(lexical); 502 | } 503 | // No more processing for meaningless tokens. 504 | if (token.type == "whitespace" || token.type == "comment") 505 | return token; 506 | // When a meaningful token is found and the lexical scope's 507 | // align is undefined, it is an aligned scope. 508 | if (!("align" in lexical)) 509 | lexical.align = true; 510 | 511 | // Execute actions until one 'consumes' the token and we can 512 | // return it. 513 | while(true) { 514 | consume = marked = false; 515 | // Take and execute the topmost action. 516 | cc.pop()(token.type, token.content); 517 | if (consume){ 518 | // Marked is used to change the style of the current token. 519 | if (marked) 520 | token.style = marked; 521 | // Here we differentiate between local and global variables. 522 | else if (token.type == "variable" && inScope(token.content)) 523 | token.style = "js-localvariable"; 524 | return token; 525 | } 526 | } 527 | } 528 | 529 | // This makes a copy of the parser state. It stores all the 530 | // stateful variables in a closure, and returns a function that 531 | // will restore them when called with a new input stream. Note 532 | // that the cc array has to be copied, because it is contantly 533 | // being modified. Lexical objects are not mutated, and context 534 | // objects are not mutated in a harmful way, so they can be shared 535 | // between runs of the parser. 536 | function copy(){ 537 | var _context = context, _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state; 538 | 539 | return function copyParser(input){ 540 | context = _context; 541 | lexical = _lexical; 542 | cc = _cc.concat([]); // copies the array 543 | column = indented = 0; 544 | tokens = tokenizeJavaScript(input, _tokenState); 545 | return parser; 546 | }; 547 | } 548 | 549 | // Helper function for pushing a number of actions onto the cc 550 | // stack in reverse order. 551 | function push(fs){ 552 | for (var i = fs.length - 1; i >= 0; i--) 553 | cc.push(fs[i]); 554 | } 555 | // cont and pass are used by the action functions to add other 556 | // actions to the stack. cont will cause the current token to be 557 | // consumed, pass will leave it for the next action. 558 | function cont(){ 559 | push(arguments); 560 | consume = true; 561 | } 562 | function pass(){ 563 | push(arguments); 564 | consume = false; 565 | } 566 | // Used to change the style of the current token. 567 | function mark(style){ 568 | marked = style; 569 | } 570 | 571 | // Push a new scope. Will automatically link the current scope. 572 | function pushcontext(){ 573 | context = {prev: context, vars: {"this": true, "arguments": true}}; 574 | } 575 | // Pop off the current scope. 576 | function popcontext(){ 577 | context = context.prev; 578 | } 579 | // Register a variable in the current scope. 580 | function register(varname){ 581 | if (context){ 582 | mark("js-variabledef"); 583 | context.vars[varname] = true; 584 | } 585 | } 586 | // Check whether a variable is defined in the current scope. 587 | function inScope(varname){ 588 | var cursor = context; 589 | while (cursor) { 590 | if (cursor.vars[varname]) 591 | return true; 592 | cursor = cursor.prev; 593 | } 594 | return false; 595 | } 596 | 597 | // Push a new lexical context of the given type. 598 | function pushlex(type, info) { 599 | var result = function(){ 600 | lexical = new JSLexical(indented, column, type, null, lexical, info) 601 | }; 602 | result.lex = true; 603 | return result; 604 | } 605 | // Pop off the current lexical context. 606 | function poplex(){ 607 | if (lexical.type == ")") 608 | indented = lexical.indented; 609 | lexical = lexical.prev; 610 | } 611 | poplex.lex = true; 612 | // The 'lex' flag on these actions is used by the 'next' function 613 | // to know they can (and have to) be ran before moving on to the 614 | // next token. 615 | 616 | // Creates an action that discards tokens until it finds one of 617 | // the given type. 618 | function expect(wanted){ 619 | return function expecting(type){ 620 | if (type == wanted) cont(); 621 | else if (wanted == ";") pass(); 622 | else cont(arguments.callee); 623 | }; 624 | } 625 | 626 | // Looks for a statement, and then calls itself. 627 | function statements(type){ 628 | return pass(statement, statements); 629 | } 630 | function expressions(type){ 631 | return pass(expression, expressions); 632 | } 633 | // Dispatches various types of statements based on the type of the 634 | // current token. 635 | function statement(type){ 636 | if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex); 637 | else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); 638 | else if (type == "keyword b") cont(pushlex("form"), statement, poplex); 639 | else if (type == "{") cont(pushlex("}"), block, poplex); 640 | else if (type == ";") cont(); 641 | else if (type == "function") cont(functiondef); 642 | else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); 643 | else if (type == "variable") cont(pushlex("stat"), maybelabel); 644 | else if (type == "switch") cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); 645 | else if (type == "case") cont(expression, expect(":")); 646 | else if (type == "default") cont(expect(":")); 647 | else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); 648 | else pass(pushlex("stat"), expression, expect(";"), poplex); 649 | } 650 | // Dispatch expression types. 651 | function expression(type){ 652 | if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); 653 | else if (type == "function") cont(functiondef); 654 | else if (type == "keyword c") cont(expression); 655 | else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator); 656 | else if (type == "operator") cont(expression); 657 | else if (type == "[") cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 658 | else if (type == "{") cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 659 | else cont(); 660 | } 661 | // Called for places where operators, function calls, or 662 | // subscripts are valid. Will skip on to the next action if none 663 | // is found. 664 | function maybeoperator(type, value){ 665 | if (type == "operator" && /\+\+|--/.test(value)) cont(maybeoperator); 666 | else if (type == "operator") cont(expression); 667 | else if (type == ";") pass(); 668 | else if (type == "(") cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 669 | else if (type == ".") cont(property, maybeoperator); 670 | else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 671 | } 672 | // When a statement starts with a variable name, it might be a 673 | // label. If no colon follows, it's a regular statement. 674 | function maybelabel(type){ 675 | if (type == ":") cont(poplex, statement); 676 | else pass(maybeoperator, expect(";"), poplex); 677 | } 678 | // Property names need to have their style adjusted -- the 679 | // tokenizer thinks they are variables. 680 | function property(type){ 681 | if (type == "variable") {mark("js-property"); cont();} 682 | } 683 | // This parses a property and its value in an object literal. 684 | function objprop(type){ 685 | if (type == "variable") mark("js-property"); 686 | if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression); 687 | } 688 | // Parses a comma-separated list of the things that are recognized 689 | // by the 'what' argument. 690 | function commasep(what, end){ 691 | function proceed(type) { 692 | if (type == ",") cont(what, proceed); 693 | else if (type == end) cont(); 694 | else cont(expect(end)); 695 | } 696 | return function commaSeparated(type) { 697 | if (type == end) cont(); 698 | else pass(what, proceed); 699 | }; 700 | } 701 | // Look for statements until a closing brace is found. 702 | function block(type){ 703 | if (type == "}") cont(); 704 | else pass(statement, block); 705 | } 706 | // Variable definitions are split into two actions -- 1 looks for 707 | // a name or the end of the definition, 2 looks for an '=' sign or 708 | // a comma. 709 | function vardef1(type, value){ 710 | if (type == "variable"){register(value); cont(vardef2);} 711 | else cont(); 712 | } 713 | function vardef2(type, value){ 714 | if (value == "=") cont(expression, vardef2); 715 | else if (type == ",") cont(vardef1); 716 | } 717 | // For loops. 718 | function forspec1(type){ 719 | if (type == "var") cont(vardef1, forspec2); 720 | else if (type == ";") pass(forspec2); 721 | else if (type == "variable") cont(formaybein); 722 | else pass(forspec2); 723 | } 724 | function formaybein(type, value){ 725 | if (value == "in") cont(expression); 726 | else cont(maybeoperator, forspec2); 727 | } 728 | function forspec2(type, value){ 729 | if (type == ";") cont(forspec3); 730 | else if (value == "in") cont(expression); 731 | else cont(expression, expect(";"), forspec3); 732 | } 733 | function forspec3(type) { 734 | if (type == ")") pass(); 735 | else cont(expression); 736 | } 737 | // A function definition creates a new context, and the variables 738 | // in its argument list have to be added to this context. 739 | function functiondef(type, value){ 740 | if (type == "variable"){register(value); cont(functiondef);} 741 | else if (type == "(") cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 742 | } 743 | function funarg(type, value){ 744 | if (type == "variable"){register(value); cont();} 745 | } 746 | 747 | return parser; 748 | } 749 | 750 | return { 751 | make: parseJS, 752 | electricChars: "{}:", 753 | configure: function(obj) { 754 | if (obj.json != null) json = obj.json; 755 | } 756 | }; 757 | })(); 758 | 759 | /* Simple parser for CSS */ 760 | 761 | this.CSSParser = Editor.Parser = (function() { 762 | var tokenizeCSS = (function() { 763 | function normal(source, setState) { 764 | var ch = source.next(); 765 | if (ch == "@") { 766 | source.nextWhileMatches(/\w/); 767 | return "css-at"; 768 | } 769 | else if (ch == "/" && source.equals("*")) { 770 | setState(inCComment); 771 | return null; 772 | } 773 | else if (ch == "<" && source.equals("!")) { 774 | setState(inSGMLComment); 775 | return null; 776 | } 777 | else if (ch == "=") { 778 | return "css-compare"; 779 | } 780 | else if (source.equals("=") && (ch == "~" || ch == "|")) { 781 | source.next(); 782 | return "css-compare"; 783 | } 784 | else if (ch == "\"" || ch == "'") { 785 | setState(inString(ch)); 786 | return null; 787 | } 788 | else if (ch == "#") { 789 | source.nextWhileMatches(/\w/); 790 | return "css-hash"; 791 | } 792 | else if (ch == "!") { 793 | source.nextWhileMatches(/[ \t]/); 794 | source.nextWhileMatches(/\w/); 795 | return "css-important"; 796 | } 797 | else if (/\d/.test(ch)) { 798 | source.nextWhileMatches(/[\w.%]/); 799 | return "css-unit"; 800 | } 801 | else if (/[,.+>*\/]/.test(ch)) { 802 | return "css-select-op"; 803 | } 804 | else if (/[;{}:\[\]]/.test(ch)) { 805 | return "css-punctuation"; 806 | } 807 | else { 808 | source.nextWhileMatches(/[\w\\\-_]/); 809 | return "css-identifier"; 810 | } 811 | } 812 | 813 | function inCComment(source, setState) { 814 | var maybeEnd = false; 815 | while (!source.endOfLine()) { 816 | var ch = source.next(); 817 | if (maybeEnd && ch == "/") { 818 | setState(normal); 819 | break; 820 | } 821 | maybeEnd = (ch == "*"); 822 | } 823 | return "css-comment"; 824 | } 825 | 826 | function inSGMLComment(source, setState) { 827 | var dashes = 0; 828 | while (!source.endOfLine()) { 829 | var ch = source.next(); 830 | if (dashes >= 2 && ch == ">") { 831 | setState(normal); 832 | break; 833 | } 834 | dashes = (ch == "-") ? dashes + 1 : 0; 835 | } 836 | return "css-comment"; 837 | } 838 | 839 | function inString(quote) { 840 | return function(source, setState) { 841 | var escaped = false; 842 | while (!source.endOfLine()) { 843 | var ch = source.next(); 844 | if (ch == quote && !escaped) 845 | break; 846 | escaped = !escaped && ch == "\\"; 847 | } 848 | if (!escaped) 849 | setState(normal); 850 | return "css-string"; 851 | }; 852 | } 853 | 854 | return function(source, startState) { 855 | return tokenizer(source, startState || normal); 856 | }; 857 | })(); 858 | 859 | function indentCSS(inBraces, inRule, base) { 860 | return function(nextChars) { 861 | if (!inBraces || /^\}/.test(nextChars)) return base; 862 | else if (inRule) return base + indentUnit * 2; 863 | else return base + indentUnit; 864 | }; 865 | } 866 | 867 | // This is a very simplistic parser -- since CSS does not really 868 | // nest, it works acceptably well, but some nicer colouroing could 869 | // be provided with a more complicated parser. 870 | function parseCSS(source, basecolumn) { 871 | basecolumn = basecolumn || 0; 872 | var tokens = tokenizeCSS(source); 873 | var inBraces = false, inRule = false, inDecl = false;; 874 | 875 | var iter = { 876 | next: function() { 877 | var token = tokens.next(), style = token.style, content = token.content; 878 | 879 | if (style == "css-hash") 880 | style = token.style = inRule ? "css-colorcode" : "css-identifier"; 881 | if (style == "css-identifier") { 882 | if (inRule) token.style = "css-value"; 883 | else if (!inBraces && !inDecl) token.style = "css-selector"; 884 | } 885 | 886 | if (content == "\n") 887 | token.indentation = indentCSS(inBraces, inRule, basecolumn); 888 | 889 | if (content == "{" && inDecl == "@media") 890 | inDecl = false; 891 | else if (content == "{") 892 | inBraces = true; 893 | else if (content == "}") 894 | inBraces = inRule = inDecl = false; 895 | else if (content == ";") 896 | inRule = inDecl = false; 897 | else if (inBraces && style != "css-comment" && style != "whitespace") 898 | inRule = true; 899 | else if (!inBraces && style == "css-at") 900 | inDecl = content; 901 | 902 | return token; 903 | }, 904 | 905 | copy: function() { 906 | var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state; 907 | return function(source) { 908 | tokens = tokenizeCSS(source, _tokenState); 909 | inBraces = _inBraces; 910 | inRule = _inRule; 911 | return iter; 912 | }; 913 | } 914 | }; 915 | return iter; 916 | } 917 | 918 | return {make: parseCSS, electricChars: "}"}; 919 | })(); 920 | 921 | /* This file defines an XML parser, with a few kludges to make it 922 | * useable for HTML. autoSelfClosers defines a set of tag names that 923 | * are expected to not have a closing tag, and doNotIndent specifies 924 | * the tags inside of which no indentation should happen (see Config 925 | * object). These can be disabled by passing the editor an object like 926 | * {useHTMLKludges: false} as parserConfig option. 927 | */ 928 | 929 | this.XMLParser = Editor.Parser = (function() { 930 | var Kludges = { 931 | autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, 932 | "meta": true, "col": true, "frame": true, "base": true, "area": true}, 933 | doNotIndent: {"pre": true, "!cdata": true} 934 | }; 935 | var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}}; 936 | var UseKludges = Kludges; 937 | var alignCDATA = false; 938 | 939 | // Simple stateful tokenizer for XML documents. Returns a 940 | // MochiKit-style iterator, with a state property that contains a 941 | // function encapsulating the current state. See tokenize.js. 942 | var tokenizeXML = (function() { 943 | function inText(source, setState) { 944 | var ch = source.next(); 945 | if (ch == "<") { 946 | if (source.equals("!")) { 947 | source.next(); 948 | if (source.equals("[")) { 949 | if (source.lookAhead("[CDATA[", true)) { 950 | setState(inBlock("xml-cdata", "]]>")); 951 | return null; 952 | } 953 | else { 954 | return "xml-text"; 955 | } 956 | } 957 | else if (source.lookAhead("--", true)) { 958 | setState(inBlock("xml-comment", "-->")); 959 | return null; 960 | } 961 | else if (source.lookAhead("DOCTYPE", true)) { 962 | source.nextWhileMatches(/[\w\._\-]/); 963 | setState(inBlock("xml-doctype", ">")); 964 | return "xml-doctype"; 965 | } 966 | else { 967 | return "xml-text"; 968 | } 969 | } 970 | else if (source.equals("?")) { 971 | source.next(); 972 | source.nextWhileMatches(/[\w\._\-]/); 973 | setState(inBlock("xml-processing", "?>")); 974 | return "xml-processing"; 975 | } 976 | else { 977 | if (source.equals("/")) source.next(); 978 | setState(inTag); 979 | return "xml-punctuation"; 980 | } 981 | } 982 | else if (ch == "&") { 983 | while (!source.endOfLine()) { 984 | if (source.next() == ";") 985 | break; 986 | } 987 | return "xml-entity"; 988 | } 989 | else { 990 | source.nextWhileMatches(/[^&<\n]/); 991 | return "xml-text"; 992 | } 993 | } 994 | 995 | function inTag(source, setState) { 996 | var ch = source.next(); 997 | if (ch == ">") { 998 | setState(inText); 999 | return "xml-punctuation"; 1000 | } 1001 | else if (/[?\/]/.test(ch) && source.equals(">")) { 1002 | source.next(); 1003 | setState(inText); 1004 | return "xml-punctuation"; 1005 | } 1006 | else if (ch == "=") { 1007 | return "xml-punctuation"; 1008 | } 1009 | else if (/[\'\"]/.test(ch)) { 1010 | setState(inAttribute(ch)); 1011 | return null; 1012 | } 1013 | else { 1014 | source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/); 1015 | return "xml-name"; 1016 | } 1017 | } 1018 | 1019 | function inAttribute(quote) { 1020 | return function(source, setState) { 1021 | while (!source.endOfLine()) { 1022 | if (source.next() == quote) { 1023 | setState(inTag); 1024 | break; 1025 | } 1026 | } 1027 | return "xml-attribute"; 1028 | }; 1029 | } 1030 | 1031 | function inBlock(style, terminator) { 1032 | return function(source, setState) { 1033 | while (!source.endOfLine()) { 1034 | if (source.lookAhead(terminator, true)) { 1035 | setState(inText); 1036 | break; 1037 | } 1038 | source.next(); 1039 | } 1040 | return style; 1041 | }; 1042 | } 1043 | 1044 | return function(source, startState) { 1045 | return tokenizer(source, startState || inText); 1046 | }; 1047 | })(); 1048 | 1049 | // The parser. The structure of this function largely follows that of 1050 | // parseJavaScript in parsejavascript.js (there is actually a bit more 1051 | // shared code than I'd like), but it is quite a bit simpler. 1052 | function parseXML(source) { 1053 | var tokens = tokenizeXML(source), token; 1054 | var cc = [base]; 1055 | var tokenNr = 0, indented = 0; 1056 | var currentTag = null, context = null; 1057 | var consume; 1058 | 1059 | function push(fs) { 1060 | for (var i = fs.length - 1; i >= 0; i--) 1061 | cc.push(fs[i]); 1062 | } 1063 | function cont() { 1064 | push(arguments); 1065 | consume = true; 1066 | } 1067 | function pass() { 1068 | push(arguments); 1069 | consume = false; 1070 | } 1071 | 1072 | function markErr() { 1073 | token.style += " xml-error"; 1074 | } 1075 | function expect(text) { 1076 | return function(style, content) { 1077 | if (content == text) cont(); 1078 | else {markErr(); cont(arguments.callee);} 1079 | }; 1080 | } 1081 | 1082 | function pushContext(tagname, startOfLine) { 1083 | var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent); 1084 | context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent}; 1085 | } 1086 | function popContext() { 1087 | context = context.prev; 1088 | } 1089 | function computeIndentation(baseContext) { 1090 | return function(nextChars, current) { 1091 | var context = baseContext; 1092 | if (context && context.noIndent) 1093 | return current; 1094 | if (alignCDATA && /")); 1114 | else if (style == "xml-cdata") { 1115 | if (!context || context.name != "!cdata") pushContext("!cdata"); 1116 | if (/\]\]>$/.test(content)) popContext(); 1117 | cont(); 1118 | } 1119 | else if (harmlessTokens.hasOwnProperty(style)) cont(); 1120 | else {markErr(); cont();} 1121 | } 1122 | function tagname(style, content) { 1123 | if (style == "xml-name") { 1124 | currentTag = content.toLowerCase(); 1125 | token.style = "xml-tagname"; 1126 | cont(); 1127 | } 1128 | else { 1129 | currentTag = null; 1130 | pass(); 1131 | } 1132 | } 1133 | function closetagname(style, content) { 1134 | if (style == "xml-name") { 1135 | token.style = "xml-tagname"; 1136 | if (context && content.toLowerCase() == context.name) popContext(); 1137 | else markErr(); 1138 | } 1139 | cont(); 1140 | } 1141 | function endtag(startOfLine) { 1142 | return function(style, content) { 1143 | if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); 1144 | else if (content == ">") {pushContext(currentTag, startOfLine); cont();} 1145 | else {markErr(); cont(arguments.callee);} 1146 | }; 1147 | } 1148 | function attributes(style) { 1149 | if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);} 1150 | else pass(); 1151 | } 1152 | function attribute(style, content) { 1153 | if (content == "=") cont(value); 1154 | else if (content == ">" || content == "/>") pass(endtag); 1155 | else pass(); 1156 | } 1157 | function value(style) { 1158 | if (style == "xml-attribute") cont(value); 1159 | else pass(); 1160 | } 1161 | 1162 | return { 1163 | indentation: function() {return indented;}, 1164 | 1165 | next: function(){ 1166 | token = tokens.next(); 1167 | if (token.style == "whitespace" && tokenNr == 0) 1168 | indented = token.value.length; 1169 | else 1170 | tokenNr++; 1171 | if (token.content == "\n") { 1172 | indented = tokenNr = 0; 1173 | token.indentation = computeIndentation(context); 1174 | } 1175 | 1176 | if (token.style == "whitespace" || token.type == "xml-comment") 1177 | return token; 1178 | 1179 | while(true){ 1180 | consume = false; 1181 | cc.pop()(token.style, token.content); 1182 | if (consume) return token; 1183 | } 1184 | }, 1185 | 1186 | copy: function(){ 1187 | var _cc = cc.concat([]), _tokenState = tokens.state, _context = context; 1188 | var parser = this; 1189 | 1190 | return function(input){ 1191 | cc = _cc.concat([]); 1192 | tokenNr = indented = 0; 1193 | context = _context; 1194 | tokens = tokenizeXML(input, _tokenState); 1195 | return parser; 1196 | }; 1197 | } 1198 | }; 1199 | } 1200 | 1201 | return { 1202 | make: parseXML, 1203 | electricChars: "/", 1204 | configure: function(config) { 1205 | if (config.useHTMLKludges != null) 1206 | UseKludges = config.useHTMLKludges ? Kludges : NoKludges; 1207 | if (config.alignCDATA) 1208 | alignCDATA = config.alignCDATA; 1209 | } 1210 | }; 1211 | })(); 1212 | 1213 | this.HTMLMixedParser = Editor.Parser = (function() { 1214 | 1215 | // tags that trigger seperate parsers 1216 | var triggers = { 1217 | "script": "JSParser", 1218 | "style": "CSSParser" 1219 | }; 1220 | 1221 | function checkDependencies() { 1222 | var parsers = ['XMLParser']; 1223 | for (var p in triggers) parsers.push(triggers[p]); 1224 | for (var i in parsers) { 1225 | if (!window[parsers[i]]) throw new Error(parsers[i] + " parser must be loaded for HTML mixed mode to work."); 1226 | } 1227 | XMLParser.configure({useHTMLKludges: true}); 1228 | } 1229 | 1230 | function parseMixed(stream) { 1231 | checkDependencies(); 1232 | var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; 1233 | var iter = {next: top, copy: copy}; 1234 | 1235 | function top() { 1236 | var token = htmlParser.next(); 1237 | if (token.content == "<") 1238 | inTag = true; 1239 | else if (token.style == "xml-tagname" && inTag === true) 1240 | inTag = token.content.toLowerCase(); 1241 | else if (token.content == ">") { 1242 | if (triggers[inTag]) { 1243 | var parser = window[triggers[inTag]]; 1244 | iter.next = local(parser, " character 1349 | // Return the next character in the stream. 1350 | peek: function() { 1351 | if (!ensureChars()) return null; 1352 | return current.charAt(pos); 1353 | }, 1354 | // next: -> character 1355 | // Get the next character, throw StopIteration if at end, check 1356 | // for unused content. 1357 | next: function() { 1358 | if (!ensureChars()) { 1359 | if (accum.length > 0) 1360 | throw "End of stringstream reached without emptying buffer ('" + accum + "')."; 1361 | else 1362 | throw StopIteration; 1363 | } 1364 | return current.charAt(pos++); 1365 | }, 1366 | // get(): -> string 1367 | // Return the characters iterated over since the last call to 1368 | // .get(). 1369 | get: function() { 1370 | var temp = accum; 1371 | accum = ""; 1372 | if (pos > 0){ 1373 | temp += current.slice(0, pos); 1374 | current = current.slice(pos); 1375 | pos = 0; 1376 | } 1377 | return temp; 1378 | }, 1379 | // Push a string back into the stream. 1380 | push: function(str) { 1381 | current = current.slice(0, pos) + str + current.slice(pos); 1382 | }, 1383 | lookAhead: function(str, consume, skipSpaces, caseInsensitive) { 1384 | function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} 1385 | str = cased(str); 1386 | var found = false; 1387 | 1388 | var _accum = accum, _pos = pos; 1389 | if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/); 1390 | 1391 | while (true) { 1392 | var end = pos + str.length, left = current.length - pos; 1393 | if (end <= current.length) { 1394 | found = str == cased(current.slice(pos, end)); 1395 | pos = end; 1396 | break; 1397 | } 1398 | else if (str.slice(0, left) == cased(current.slice(pos))) { 1399 | accum += current; current = ""; 1400 | try {current = source.next();} 1401 | catch (e) {if (e != StopIteration) throw e; break;} 1402 | pos = 0; 1403 | str = str.slice(left); 1404 | } 1405 | else { 1406 | break; 1407 | } 1408 | } 1409 | 1410 | if (!(found && consume)) { 1411 | current = accum.slice(_accum.length) + current; 1412 | pos = _pos; 1413 | accum = _accum; 1414 | } 1415 | 1416 | return found; 1417 | }, 1418 | // Wont't match past end of line. 1419 | lookAheadRegex: function(regex, consume) { 1420 | if (regex.source.charAt(0) != "^") 1421 | throw new Error("Regexps passed to lookAheadRegex must start with ^"); 1422 | 1423 | // Fetch the rest of the line 1424 | while (current.indexOf("\n", pos) == -1) { 1425 | try {current += source.next();} 1426 | catch (e) {if (e != StopIteration) throw e; break;} 1427 | } 1428 | var matched = current.slice(pos).match(regex); 1429 | if (matched && consume) pos += matched[0].length; 1430 | return matched; 1431 | }, 1432 | 1433 | // Utils built on top of the above 1434 | // more: -> boolean 1435 | // Produce true if the stream isn't empty. 1436 | more: function() { 1437 | return this.peek() !== null; 1438 | }, 1439 | applies: function(test) { 1440 | var next = this.peek(); 1441 | return (next !== null && test(next)); 1442 | }, 1443 | nextWhile: function(test) { 1444 | var next; 1445 | while ((next = this.peek()) !== null && test(next)) 1446 | this.next(); 1447 | }, 1448 | matches: function(re) { 1449 | var next = this.peek(); 1450 | return (next !== null && re.test(next)); 1451 | }, 1452 | nextWhileMatches: function(re) { 1453 | var next; 1454 | while ((next = this.peek()) !== null && re.test(next)) 1455 | this.next(); 1456 | }, 1457 | equals: function(ch) { 1458 | return ch === this.peek(); 1459 | }, 1460 | endOfLine: function() { 1461 | var next = this.peek(); 1462 | return next == null || next == "\n"; 1463 | } 1464 | }; 1465 | }; 1466 | 1467 | return this; 1468 | })(); 1469 | -------------------------------------------------------------------------------- /chrome/content/codemirror/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2010 Marijn Haverbeke 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any 5 | damages arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any 8 | purpose, including commercial applications, and to alter it and 9 | redistribute it freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must 12 | not claim that you wrote the original software. If you use this 13 | software in a product, an acknowledgment in the product 14 | documentation would be appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must 17 | not be misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source 20 | distribution. 21 | 22 | Marijn Haverbeke 23 | marijnh@gmail.com 24 | -------------------------------------------------------------------------------- /chrome/content/codemirror/parsecss.js: -------------------------------------------------------------------------------- 1 | /* Simple parser for CSS */ 2 | 3 | var CSSParser = Editor.Parser = (function() { 4 | var tokenizeCSS = (function() { 5 | function normal(source, setState) { 6 | var ch = source.next(); 7 | if (ch == "@") { 8 | source.nextWhileMatches(/\w/); 9 | return "css-at"; 10 | } 11 | else if (ch == "/" && source.equals("*")) { 12 | setState(inCComment); 13 | return null; 14 | } 15 | else if (ch == "<" && source.equals("!")) { 16 | setState(inSGMLComment); 17 | return null; 18 | } 19 | else if (ch == "=") { 20 | return "css-compare"; 21 | } 22 | else if (source.equals("=") && (ch == "~" || ch == "|")) { 23 | source.next(); 24 | return "css-compare"; 25 | } 26 | else if (ch == "\"" || ch == "'") { 27 | setState(inString(ch)); 28 | return null; 29 | } 30 | else if (ch == "#") { 31 | source.nextWhileMatches(/\w/); 32 | return "css-hash"; 33 | } 34 | else if (ch == "!") { 35 | source.nextWhileMatches(/[ \t]/); 36 | source.nextWhileMatches(/\w/); 37 | return "css-important"; 38 | } 39 | else if (/\d/.test(ch)) { 40 | source.nextWhileMatches(/[\w.%]/); 41 | return "css-unit"; 42 | } 43 | else if (/[,.+>*\/]/.test(ch)) { 44 | return "css-select-op"; 45 | } 46 | else if (/[;{}:\[\]]/.test(ch)) { 47 | return "css-punctuation"; 48 | } 49 | else { 50 | source.nextWhileMatches(/[\w\\\-_]/); 51 | return "css-identifier"; 52 | } 53 | } 54 | 55 | function inCComment(source, setState) { 56 | var maybeEnd = false; 57 | while (!source.endOfLine()) { 58 | var ch = source.next(); 59 | if (maybeEnd && ch == "/") { 60 | setState(normal); 61 | break; 62 | } 63 | maybeEnd = (ch == "*"); 64 | } 65 | return "css-comment"; 66 | } 67 | 68 | function inSGMLComment(source, setState) { 69 | var dashes = 0; 70 | while (!source.endOfLine()) { 71 | var ch = source.next(); 72 | if (dashes >= 2 && ch == ">") { 73 | setState(normal); 74 | break; 75 | } 76 | dashes = (ch == "-") ? dashes + 1 : 0; 77 | } 78 | return "css-comment"; 79 | } 80 | 81 | function inString(quote) { 82 | return function(source, setState) { 83 | var escaped = false; 84 | while (!source.endOfLine()) { 85 | var ch = source.next(); 86 | if (ch == quote && !escaped) 87 | break; 88 | escaped = !escaped && ch == "\\"; 89 | } 90 | if (!escaped) 91 | setState(normal); 92 | return "css-string"; 93 | }; 94 | } 95 | 96 | return function(source, startState) { 97 | return tokenizer(source, startState || normal); 98 | }; 99 | })(); 100 | 101 | function indentCSS(inBraces, inRule, base) { 102 | return function(nextChars) { 103 | if (!inBraces || /^\}/.test(nextChars)) return base; 104 | else if (inRule) return base + indentUnit * 2; 105 | else return base + indentUnit; 106 | }; 107 | } 108 | 109 | // This is a very simplistic parser -- since CSS does not really 110 | // nest, it works acceptably well, but some nicer colouroing could 111 | // be provided with a more complicated parser. 112 | function parseCSS(source, basecolumn) { 113 | basecolumn = basecolumn || 0; 114 | var tokens = tokenizeCSS(source); 115 | var inBraces = false, inRule = false, inDecl = false;; 116 | 117 | var iter = { 118 | next: function() { 119 | var token = tokens.next(), style = token.style, content = token.content; 120 | 121 | if (style == "css-hash") 122 | style = token.style = inRule ? "css-colorcode" : "css-identifier"; 123 | if (style == "css-identifier") { 124 | if (inRule) token.style = "css-value"; 125 | else if (!inBraces && !inDecl) token.style = "css-selector"; 126 | } 127 | 128 | if (content == "\n") 129 | token.indentation = indentCSS(inBraces, inRule, basecolumn); 130 | 131 | if (content == "{" && inDecl == "@media") 132 | inDecl = false; 133 | else if (content == "{") 134 | inBraces = true; 135 | else if (content == "}") 136 | inBraces = inRule = inDecl = false; 137 | else if (content == ";") 138 | inRule = inDecl = false; 139 | else if (inBraces && style != "css-comment" && style != "whitespace") 140 | inRule = true; 141 | else if (!inBraces && style == "css-at") 142 | inDecl = content; 143 | 144 | return token; 145 | }, 146 | 147 | copy: function() { 148 | var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state; 149 | return function(source) { 150 | tokens = tokenizeCSS(source, _tokenState); 151 | inBraces = _inBraces; 152 | inRule = _inRule; 153 | return iter; 154 | }; 155 | } 156 | }; 157 | return iter; 158 | } 159 | 160 | return {make: parseCSS, electricChars: "}"}; 161 | })(); 162 | -------------------------------------------------------------------------------- /chrome/content/codemirror/parsehtmlmixed.js: -------------------------------------------------------------------------------- 1 | var HTMLMixedParser = Editor.Parser = (function() { 2 | 3 | // tags that trigger seperate parsers 4 | var triggers = { 5 | "script": "JSParser", 6 | "style": "CSSParser" 7 | }; 8 | 9 | function checkDependencies() { 10 | var parsers = ['XMLParser']; 11 | for (var p in triggers) parsers.push(triggers[p]); 12 | for (var i in parsers) { 13 | if (!window[parsers[i]]) throw new Error(parsers[i] + " parser must be loaded for HTML mixed mode to work."); 14 | } 15 | XMLParser.configure({useHTMLKludges: true}); 16 | } 17 | 18 | function parseMixed(stream) { 19 | checkDependencies(); 20 | var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; 21 | var iter = {next: top, copy: copy}; 22 | 23 | function top() { 24 | var token = htmlParser.next(); 25 | if (token.content == "<") 26 | inTag = true; 27 | else if (token.style == "xml-tagname" && inTag === true) 28 | inTag = token.content.toLowerCase(); 29 | else if (token.content == ">") { 30 | if (triggers[inTag]) { 31 | var parser = window[triggers[inTag]]; 32 | iter.next = local(parser, "= 0; i--) 155 | cc.push(fs[i]); 156 | } 157 | // cont and pass are used by the action functions to add other 158 | // actions to the stack. cont will cause the current token to be 159 | // consumed, pass will leave it for the next action. 160 | function cont(){ 161 | push(arguments); 162 | consume = true; 163 | } 164 | function pass(){ 165 | push(arguments); 166 | consume = false; 167 | } 168 | // Used to change the style of the current token. 169 | function mark(style){ 170 | marked = style; 171 | } 172 | 173 | // Push a new scope. Will automatically link the current scope. 174 | function pushcontext(){ 175 | context = {prev: context, vars: {"this": true, "arguments": true}}; 176 | } 177 | // Pop off the current scope. 178 | function popcontext(){ 179 | context = context.prev; 180 | } 181 | // Register a variable in the current scope. 182 | function register(varname){ 183 | if (context){ 184 | mark("js-variabledef"); 185 | context.vars[varname] = true; 186 | } 187 | } 188 | // Check whether a variable is defined in the current scope. 189 | function inScope(varname){ 190 | var cursor = context; 191 | while (cursor) { 192 | if (cursor.vars[varname]) 193 | return true; 194 | cursor = cursor.prev; 195 | } 196 | return false; 197 | } 198 | 199 | // Push a new lexical context of the given type. 200 | function pushlex(type, info) { 201 | var result = function(){ 202 | lexical = new JSLexical(indented, column, type, null, lexical, info) 203 | }; 204 | result.lex = true; 205 | return result; 206 | } 207 | // Pop off the current lexical context. 208 | function poplex(){ 209 | if (lexical.type == ")") 210 | indented = lexical.indented; 211 | lexical = lexical.prev; 212 | } 213 | poplex.lex = true; 214 | // The 'lex' flag on these actions is used by the 'next' function 215 | // to know they can (and have to) be ran before moving on to the 216 | // next token. 217 | 218 | // Creates an action that discards tokens until it finds one of 219 | // the given type. 220 | function expect(wanted){ 221 | return function expecting(type){ 222 | if (type == wanted) cont(); 223 | else if (wanted == ";") pass(); 224 | else cont(arguments.callee); 225 | }; 226 | } 227 | 228 | // Looks for a statement, and then calls itself. 229 | function statements(type){ 230 | return pass(statement, statements); 231 | } 232 | function expressions(type){ 233 | return pass(expression, expressions); 234 | } 235 | // Dispatches various types of statements based on the type of the 236 | // current token. 237 | function statement(type){ 238 | if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex); 239 | else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); 240 | else if (type == "keyword b") cont(pushlex("form"), statement, poplex); 241 | else if (type == "{") cont(pushlex("}"), block, poplex); 242 | else if (type == ";") cont(); 243 | else if (type == "function") cont(functiondef); 244 | else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); 245 | else if (type == "variable") cont(pushlex("stat"), maybelabel); 246 | else if (type == "switch") cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); 247 | else if (type == "case") cont(expression, expect(":")); 248 | else if (type == "default") cont(expect(":")); 249 | else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); 250 | else pass(pushlex("stat"), expression, expect(";"), poplex); 251 | } 252 | // Dispatch expression types. 253 | function expression(type){ 254 | if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); 255 | else if (type == "function") cont(functiondef); 256 | else if (type == "keyword c") cont(expression); 257 | else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator); 258 | else if (type == "operator") cont(expression); 259 | else if (type == "[") cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 260 | else if (type == "{") cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 261 | else cont(); 262 | } 263 | // Called for places where operators, function calls, or 264 | // subscripts are valid. Will skip on to the next action if none 265 | // is found. 266 | function maybeoperator(type, value){ 267 | if (type == "operator" && /\+\+|--/.test(value)) cont(maybeoperator); 268 | else if (type == "operator") cont(expression); 269 | else if (type == ";") pass(); 270 | else if (type == "(") cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 271 | else if (type == ".") cont(property, maybeoperator); 272 | else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 273 | } 274 | // When a statement starts with a variable name, it might be a 275 | // label. If no colon follows, it's a regular statement. 276 | function maybelabel(type){ 277 | if (type == ":") cont(poplex, statement); 278 | else pass(maybeoperator, expect(";"), poplex); 279 | } 280 | // Property names need to have their style adjusted -- the 281 | // tokenizer thinks they are variables. 282 | function property(type){ 283 | if (type == "variable") {mark("js-property"); cont();} 284 | } 285 | // This parses a property and its value in an object literal. 286 | function objprop(type){ 287 | if (type == "variable") mark("js-property"); 288 | if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression); 289 | } 290 | // Parses a comma-separated list of the things that are recognized 291 | // by the 'what' argument. 292 | function commasep(what, end){ 293 | function proceed(type) { 294 | if (type == ",") cont(what, proceed); 295 | else if (type == end) cont(); 296 | else cont(expect(end)); 297 | } 298 | return function commaSeparated(type) { 299 | if (type == end) cont(); 300 | else pass(what, proceed); 301 | }; 302 | } 303 | // Look for statements until a closing brace is found. 304 | function block(type){ 305 | if (type == "}") cont(); 306 | else pass(statement, block); 307 | } 308 | // Variable definitions are split into two actions -- 1 looks for 309 | // a name or the end of the definition, 2 looks for an '=' sign or 310 | // a comma. 311 | function vardef1(type, value){ 312 | if (type == "variable"){register(value); cont(vardef2);} 313 | else cont(); 314 | } 315 | function vardef2(type, value){ 316 | if (value == "=") cont(expression, vardef2); 317 | else if (type == ",") cont(vardef1); 318 | } 319 | // For loops. 320 | function forspec1(type){ 321 | if (type == "var") cont(vardef1, forspec2); 322 | else if (type == ";") pass(forspec2); 323 | else if (type == "variable") cont(formaybein); 324 | else pass(forspec2); 325 | } 326 | function formaybein(type, value){ 327 | if (value == "in") cont(expression); 328 | else cont(maybeoperator, forspec2); 329 | } 330 | function forspec2(type, value){ 331 | if (type == ";") cont(forspec3); 332 | else if (value == "in") cont(expression); 333 | else cont(expression, expect(";"), forspec3); 334 | } 335 | function forspec3(type) { 336 | if (type == ")") pass(); 337 | else cont(expression); 338 | } 339 | // A function definition creates a new context, and the variables 340 | // in its argument list have to be added to this context. 341 | function functiondef(type, value){ 342 | if (type == "variable"){register(value); cont(functiondef);} 343 | else if (type == "(") cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 344 | } 345 | function funarg(type, value){ 346 | if (type == "variable"){register(value); cont();} 347 | } 348 | 349 | return parser; 350 | } 351 | 352 | return { 353 | make: parseJS, 354 | electricChars: "{}:", 355 | configure: function(obj) { 356 | if (obj.json != null) json = obj.json; 357 | } 358 | }; 359 | })(); 360 | -------------------------------------------------------------------------------- /chrome/content/codemirror/parsexml.js: -------------------------------------------------------------------------------- 1 | /* This file defines an XML parser, with a few kludges to make it 2 | * useable for HTML. autoSelfClosers defines a set of tag names that 3 | * are expected to not have a closing tag, and doNotIndent specifies 4 | * the tags inside of which no indentation should happen (see Config 5 | * object). These can be disabled by passing the editor an object like 6 | * {useHTMLKludges: false} as parserConfig option. 7 | */ 8 | 9 | var XMLParser = Editor.Parser = (function() { 10 | var Kludges = { 11 | autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, 12 | "meta": true, "col": true, "frame": true, "base": true, "area": true}, 13 | doNotIndent: {"pre": true, "!cdata": true} 14 | }; 15 | var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}}; 16 | var UseKludges = Kludges; 17 | var alignCDATA = false; 18 | 19 | // Simple stateful tokenizer for XML documents. Returns a 20 | // MochiKit-style iterator, with a state property that contains a 21 | // function encapsulating the current state. See tokenize.js. 22 | var tokenizeXML = (function() { 23 | function inText(source, setState) { 24 | var ch = source.next(); 25 | if (ch == "<") { 26 | if (source.equals("!")) { 27 | source.next(); 28 | if (source.equals("[")) { 29 | if (source.lookAhead("[CDATA[", true)) { 30 | setState(inBlock("xml-cdata", "]]>")); 31 | return null; 32 | } 33 | else { 34 | return "xml-text"; 35 | } 36 | } 37 | else if (source.lookAhead("--", true)) { 38 | setState(inBlock("xml-comment", "-->")); 39 | return null; 40 | } 41 | else if (source.lookAhead("DOCTYPE", true)) { 42 | source.nextWhileMatches(/[\w\._\-]/); 43 | setState(inBlock("xml-doctype", ">")); 44 | return "xml-doctype"; 45 | } 46 | else { 47 | return "xml-text"; 48 | } 49 | } 50 | else if (source.equals("?")) { 51 | source.next(); 52 | source.nextWhileMatches(/[\w\._\-]/); 53 | setState(inBlock("xml-processing", "?>")); 54 | return "xml-processing"; 55 | } 56 | else { 57 | if (source.equals("/")) source.next(); 58 | setState(inTag); 59 | return "xml-punctuation"; 60 | } 61 | } 62 | else if (ch == "&") { 63 | while (!source.endOfLine()) { 64 | if (source.next() == ";") 65 | break; 66 | } 67 | return "xml-entity"; 68 | } 69 | else { 70 | source.nextWhileMatches(/[^&<\n]/); 71 | return "xml-text"; 72 | } 73 | } 74 | 75 | function inTag(source, setState) { 76 | var ch = source.next(); 77 | if (ch == ">") { 78 | setState(inText); 79 | return "xml-punctuation"; 80 | } 81 | else if (/[?\/]/.test(ch) && source.equals(">")) { 82 | source.next(); 83 | setState(inText); 84 | return "xml-punctuation"; 85 | } 86 | else if (ch == "=") { 87 | return "xml-punctuation"; 88 | } 89 | else if (/[\'\"]/.test(ch)) { 90 | setState(inAttribute(ch)); 91 | return null; 92 | } 93 | else { 94 | source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/); 95 | return "xml-name"; 96 | } 97 | } 98 | 99 | function inAttribute(quote) { 100 | return function(source, setState) { 101 | while (!source.endOfLine()) { 102 | if (source.next() == quote) { 103 | setState(inTag); 104 | break; 105 | } 106 | } 107 | return "xml-attribute"; 108 | }; 109 | } 110 | 111 | function inBlock(style, terminator) { 112 | return function(source, setState) { 113 | while (!source.endOfLine()) { 114 | if (source.lookAhead(terminator, true)) { 115 | setState(inText); 116 | break; 117 | } 118 | source.next(); 119 | } 120 | return style; 121 | }; 122 | } 123 | 124 | return function(source, startState) { 125 | return tokenizer(source, startState || inText); 126 | }; 127 | })(); 128 | 129 | // The parser. The structure of this function largely follows that of 130 | // parseJavaScript in parsejavascript.js (there is actually a bit more 131 | // shared code than I'd like), but it is quite a bit simpler. 132 | function parseXML(source) { 133 | var tokens = tokenizeXML(source), token; 134 | var cc = [base]; 135 | var tokenNr = 0, indented = 0; 136 | var currentTag = null, context = null; 137 | var consume; 138 | 139 | function push(fs) { 140 | for (var i = fs.length - 1; i >= 0; i--) 141 | cc.push(fs[i]); 142 | } 143 | function cont() { 144 | push(arguments); 145 | consume = true; 146 | } 147 | function pass() { 148 | push(arguments); 149 | consume = false; 150 | } 151 | 152 | function markErr() { 153 | token.style += " xml-error"; 154 | } 155 | function expect(text) { 156 | return function(style, content) { 157 | if (content == text) cont(); 158 | else {markErr(); cont(arguments.callee);} 159 | }; 160 | } 161 | 162 | function pushContext(tagname, startOfLine) { 163 | var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent); 164 | context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent}; 165 | } 166 | function popContext() { 167 | context = context.prev; 168 | } 169 | function computeIndentation(baseContext) { 170 | return function(nextChars, current) { 171 | var context = baseContext; 172 | if (context && context.noIndent) 173 | return current; 174 | if (alignCDATA && /")); 194 | else if (style == "xml-cdata") { 195 | if (!context || context.name != "!cdata") pushContext("!cdata"); 196 | if (/\]\]>$/.test(content)) popContext(); 197 | cont(); 198 | } 199 | else if (harmlessTokens.hasOwnProperty(style)) cont(); 200 | else {markErr(); cont();} 201 | } 202 | function tagname(style, content) { 203 | if (style == "xml-name") { 204 | currentTag = content.toLowerCase(); 205 | token.style = "xml-tagname"; 206 | cont(); 207 | } 208 | else { 209 | currentTag = null; 210 | pass(); 211 | } 212 | } 213 | function closetagname(style, content) { 214 | if (style == "xml-name") { 215 | token.style = "xml-tagname"; 216 | if (context && content.toLowerCase() == context.name) popContext(); 217 | else markErr(); 218 | } 219 | cont(); 220 | } 221 | function endtag(startOfLine) { 222 | return function(style, content) { 223 | if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); 224 | else if (content == ">") {pushContext(currentTag, startOfLine); cont();} 225 | else {markErr(); cont(arguments.callee);} 226 | }; 227 | } 228 | function attributes(style) { 229 | if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);} 230 | else pass(); 231 | } 232 | function attribute(style, content) { 233 | if (content == "=") cont(value); 234 | else if (content == ">" || content == "/>") pass(endtag); 235 | else pass(); 236 | } 237 | function value(style) { 238 | if (style == "xml-attribute") cont(value); 239 | else pass(); 240 | } 241 | 242 | return { 243 | indentation: function() {return indented;}, 244 | 245 | next: function(){ 246 | token = tokens.next(); 247 | if (token.style == "whitespace" && tokenNr == 0) 248 | indented = token.value.length; 249 | else 250 | tokenNr++; 251 | if (token.content == "\n") { 252 | indented = tokenNr = 0; 253 | token.indentation = computeIndentation(context); 254 | } 255 | 256 | if (token.style == "whitespace" || token.type == "xml-comment") 257 | return token; 258 | 259 | while(true){ 260 | consume = false; 261 | cc.pop()(token.style, token.content); 262 | if (consume) return token; 263 | } 264 | }, 265 | 266 | copy: function(){ 267 | var _cc = cc.concat([]), _tokenState = tokens.state, _context = context; 268 | var parser = this; 269 | 270 | return function(input){ 271 | cc = _cc.concat([]); 272 | tokenNr = indented = 0; 273 | context = _context; 274 | tokens = tokenizeXML(input, _tokenState); 275 | return parser; 276 | }; 277 | } 278 | }; 279 | } 280 | 281 | return { 282 | make: parseXML, 283 | electricChars: "/", 284 | configure: function(config) { 285 | if (config.useHTMLKludges != null) 286 | UseKludges = config.useHTMLKludges ? Kludges : NoKludges; 287 | if (config.alignCDATA) 288 | alignCDATA = config.alignCDATA; 289 | } 290 | }; 291 | })(); 292 | -------------------------------------------------------------------------------- /chrome/content/codemirror/stringstream.js: -------------------------------------------------------------------------------- 1 | /* String streams are the things fed to parsers (which can feed them 2 | * to a tokenizer if they want). They provide peek and next methods 3 | * for looking at the current character (next 'consumes' this 4 | * character, peek does not), and a get method for retrieving all the 5 | * text that was consumed since the last time get was called. 6 | * 7 | * An easy mistake to make is to let a StopIteration exception finish 8 | * the token stream while there are still characters pending in the 9 | * string stream (hitting the end of the buffer while parsing a 10 | * token). To make it easier to detect such errors, the stringstreams 11 | * throw an exception when this happens. 12 | */ 13 | 14 | // Make a stringstream stream out of an iterator that returns strings. 15 | // This is applied to the result of traverseDOM (see codemirror.js), 16 | // and the resulting stream is fed to the parser. 17 | var stringStream = function(source){ 18 | // String that's currently being iterated over. 19 | var current = ""; 20 | // Position in that string. 21 | var pos = 0; 22 | // Accumulator for strings that have been iterated over but not 23 | // get()-ed yet. 24 | var accum = ""; 25 | // Make sure there are more characters ready, or throw 26 | // StopIteration. 27 | function ensureChars() { 28 | while (pos == current.length) { 29 | accum += current; 30 | current = ""; // In case source.next() throws 31 | pos = 0; 32 | try {current = source.next();} 33 | catch (e) { 34 | if (e != StopIteration) throw e; 35 | else return false; 36 | } 37 | } 38 | return true; 39 | } 40 | 41 | return { 42 | // peek: -> character 43 | // Return the next character in the stream. 44 | peek: function() { 45 | if (!ensureChars()) return null; 46 | return current.charAt(pos); 47 | }, 48 | // next: -> character 49 | // Get the next character, throw StopIteration if at end, check 50 | // for unused content. 51 | next: function() { 52 | if (!ensureChars()) { 53 | if (accum.length > 0) 54 | throw "End of stringstream reached without emptying buffer ('" + accum + "')."; 55 | else 56 | throw StopIteration; 57 | } 58 | return current.charAt(pos++); 59 | }, 60 | // get(): -> string 61 | // Return the characters iterated over since the last call to 62 | // .get(). 63 | get: function() { 64 | var temp = accum; 65 | accum = ""; 66 | if (pos > 0){ 67 | temp += current.slice(0, pos); 68 | current = current.slice(pos); 69 | pos = 0; 70 | } 71 | return temp; 72 | }, 73 | // Push a string back into the stream. 74 | push: function(str) { 75 | current = current.slice(0, pos) + str + current.slice(pos); 76 | }, 77 | lookAhead: function(str, consume, skipSpaces, caseInsensitive) { 78 | function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} 79 | str = cased(str); 80 | var found = false; 81 | 82 | var _accum = accum, _pos = pos; 83 | if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/); 84 | 85 | while (true) { 86 | var end = pos + str.length, left = current.length - pos; 87 | if (end <= current.length) { 88 | found = str == cased(current.slice(pos, end)); 89 | pos = end; 90 | break; 91 | } 92 | else if (str.slice(0, left) == cased(current.slice(pos))) { 93 | accum += current; current = ""; 94 | try {current = source.next();} 95 | catch (e) {if (e != StopIteration) throw e; break;} 96 | pos = 0; 97 | str = str.slice(left); 98 | } 99 | else { 100 | break; 101 | } 102 | } 103 | 104 | if (!(found && consume)) { 105 | current = accum.slice(_accum.length) + current; 106 | pos = _pos; 107 | accum = _accum; 108 | } 109 | 110 | return found; 111 | }, 112 | // Wont't match past end of line. 113 | lookAheadRegex: function(regex, consume) { 114 | if (regex.source.charAt(0) != "^") 115 | throw new Error("Regexps passed to lookAheadRegex must start with ^"); 116 | 117 | // Fetch the rest of the line 118 | while (current.indexOf("\n", pos) == -1) { 119 | try {current += source.next();} 120 | catch (e) {if (e != StopIteration) throw e; break;} 121 | } 122 | var matched = current.slice(pos).match(regex); 123 | if (matched && consume) pos += matched[0].length; 124 | return matched; 125 | }, 126 | 127 | // Utils built on top of the above 128 | // more: -> boolean 129 | // Produce true if the stream isn't empty. 130 | more: function() { 131 | return this.peek() !== null; 132 | }, 133 | applies: function(test) { 134 | var next = this.peek(); 135 | return (next !== null && test(next)); 136 | }, 137 | nextWhile: function(test) { 138 | var next; 139 | while ((next = this.peek()) !== null && test(next)) 140 | this.next(); 141 | }, 142 | matches: function(re) { 143 | var next = this.peek(); 144 | return (next !== null && re.test(next)); 145 | }, 146 | nextWhileMatches: function(re) { 147 | var next; 148 | while ((next = this.peek()) !== null && re.test(next)) 149 | this.next(); 150 | }, 151 | equals: function(ch) { 152 | return ch === this.peek(); 153 | }, 154 | endOfLine: function() { 155 | var next = this.peek(); 156 | return next == null || next == "\n"; 157 | } 158 | }; 159 | }; 160 | -------------------------------------------------------------------------------- /chrome/content/codemirror/tokenize.js: -------------------------------------------------------------------------------- 1 | // A framework for simple tokenizers. Takes care of newlines and 2 | // white-space, and of getting the text from the source stream into 3 | // the token object. A state is a function of two arguments -- a 4 | // string stream and a setState function. The second can be used to 5 | // change the tokenizer's state, and can be ignored for stateless 6 | // tokenizers. This function should advance the stream over a token 7 | // and return a string or object containing information about the next 8 | // token, or null to pass and have the (new) state be called to finish 9 | // the token. When a string is given, it is wrapped in a {style, type} 10 | // object. In the resulting object, the characters consumed are stored 11 | // under the content property. Any whitespace following them is also 12 | // automatically consumed, and added to the value property. (Thus, 13 | // content is the actual meaningful part of the token, while value 14 | // contains all the text it spans.) 15 | 16 | function tokenizer(source, state) { 17 | // Newlines are always a separate token. 18 | function isWhiteSpace(ch) { 19 | // The messy regexp is because IE's regexp matcher is of the 20 | // opinion that non-breaking spaces are no whitespace. 21 | return ch != "\n" && /^[\s\u00a0]*$/.test(ch); 22 | } 23 | 24 | var tokenizer = { 25 | state: state, 26 | 27 | take: function(type) { 28 | if (typeof(type) == "string") 29 | type = {style: type, type: type}; 30 | 31 | type.content = (type.content || "") + source.get(); 32 | if (!/\n$/.test(type.content)) 33 | source.nextWhile(isWhiteSpace); 34 | type.value = type.content + source.get(); 35 | return type; 36 | }, 37 | 38 | next: function () { 39 | if (!source.more()) throw StopIteration; 40 | 41 | var type; 42 | if (source.equals("\n")) { 43 | source.next(); 44 | return this.take("whitespace"); 45 | } 46 | 47 | if (source.applies(isWhiteSpace)) 48 | type = "whitespace"; 49 | else 50 | while (!type) 51 | type = this.state(source, function(s) {tokenizer.state = s;}); 52 | 53 | return this.take(type); 54 | } 55 | }; 56 | return tokenizer; 57 | } 58 | -------------------------------------------------------------------------------- /chrome/content/codemirror/tokenizejavascript.js: -------------------------------------------------------------------------------- 1 | /* Tokenizer for JavaScript code */ 2 | 3 | var tokenizeJavaScript = (function() { 4 | // Advance the stream until the given character (not preceded by a 5 | // backslash) is encountered, or the end of the line is reached. 6 | function nextUntilUnescaped(source, end) { 7 | var escaped = false; 8 | while (!source.endOfLine()) { 9 | var next = source.next(); 10 | if (next == end && !escaped) 11 | return false; 12 | escaped = !escaped && next == "\\"; 13 | } 14 | return escaped; 15 | } 16 | 17 | // A map of JavaScript's keywords. The a/b/c keyword distinction is 18 | // very rough, but it gives the parser enough information to parse 19 | // correct code correctly (we don't care that much how we parse 20 | // incorrect code). The style information included in these objects 21 | // is used by the highlighter to pick the correct CSS style for a 22 | // token. 23 | var keywords = function(){ 24 | function result(type, style){ 25 | return {type: type, style: "js-" + style}; 26 | } 27 | // keywords that take a parenthised expression, and then a 28 | // statement (if) 29 | var keywordA = result("keyword a", "keyword"); 30 | // keywords that take just a statement (else) 31 | var keywordB = result("keyword b", "keyword"); 32 | // keywords that optionally take an expression, and form a 33 | // statement (return) 34 | var keywordC = result("keyword c", "keyword"); 35 | var operator = result("operator", "keyword"); 36 | var atom = result("atom", "atom"); 37 | return { 38 | "if": keywordA, "while": keywordA, "with": keywordA, 39 | "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB, 40 | "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC, 41 | "in": operator, "typeof": operator, "instanceof": operator, 42 | "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"), 43 | "for": result("for", "keyword"), "switch": result("switch", "keyword"), 44 | "case": result("case", "keyword"), "default": result("default", "keyword"), 45 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 46 | }; 47 | }(); 48 | 49 | // Some helper regexps 50 | var isOperatorChar = /[+\-*&%=<>!?|]/; 51 | var isHexDigit = /[0-9A-Fa-f]/; 52 | var isWordChar = /[\w\$_]/; 53 | 54 | // Wrapper around jsToken that helps maintain parser state (whether 55 | // we are inside of a multi-line comment and whether the next token 56 | // could be a regular expression). 57 | function jsTokenState(inside, regexp) { 58 | return function(source, setState) { 59 | var newInside = inside; 60 | var type = jsToken(inside, regexp, source, function(c) {newInside = c;}); 61 | var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/); 62 | if (newRegexp != regexp || newInside != inside) 63 | setState(jsTokenState(newInside, newRegexp)); 64 | return type; 65 | }; 66 | } 67 | 68 | // The token reader, intended to be used by the tokenizer from 69 | // tokenize.js (through jsTokenState). Advances the source stream 70 | // over a token, and returns an object containing the type and style 71 | // of that token. 72 | function jsToken(inside, regexp, source, setInside) { 73 | function readHexNumber(){ 74 | source.next(); // skip the 'x' 75 | source.nextWhileMatches(isHexDigit); 76 | return {type: "number", style: "js-atom"}; 77 | } 78 | 79 | function readNumber() { 80 | source.nextWhileMatches(/[0-9]/); 81 | if (source.equals(".")){ 82 | source.next(); 83 | source.nextWhileMatches(/[0-9]/); 84 | } 85 | if (source.equals("e") || source.equals("E")){ 86 | source.next(); 87 | if (source.equals("-")) 88 | source.next(); 89 | source.nextWhileMatches(/[0-9]/); 90 | } 91 | return {type: "number", style: "js-atom"}; 92 | } 93 | // Read a word, look it up in keywords. If not found, it is a 94 | // variable, otherwise it is a keyword of the type found. 95 | function readWord() { 96 | source.nextWhileMatches(isWordChar); 97 | var word = source.get(); 98 | var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word]; 99 | return known ? {type: known.type, style: known.style, content: word} : 100 | {type: "variable", style: "js-variable", content: word}; 101 | } 102 | function readRegexp() { 103 | nextUntilUnescaped(source, "/"); 104 | source.nextWhileMatches(/[gimy]/); // 'y' is "sticky" option in Mozilla 105 | return {type: "regexp", style: "js-string"}; 106 | } 107 | // Mutli-line comments are tricky. We want to return the newlines 108 | // embedded in them as regular newline tokens, and then continue 109 | // returning a comment token for every line of the comment. So 110 | // some state has to be saved (inside) to indicate whether we are 111 | // inside a /* */ sequence. 112 | function readMultilineComment(start){ 113 | var newInside = "/*"; 114 | var maybeEnd = (start == "*"); 115 | while (true) { 116 | if (source.endOfLine()) 117 | break; 118 | var next = source.next(); 119 | if (next == "/" && maybeEnd){ 120 | newInside = null; 121 | break; 122 | } 123 | maybeEnd = (next == "*"); 124 | } 125 | setInside(newInside); 126 | return {type: "comment", style: "js-comment"}; 127 | } 128 | function readOperator() { 129 | source.nextWhileMatches(isOperatorChar); 130 | return {type: "operator", style: "js-operator"}; 131 | } 132 | function readString(quote) { 133 | var endBackSlash = nextUntilUnescaped(source, quote); 134 | setInside(endBackSlash ? quote : null); 135 | return {type: "string", style: "js-string"}; 136 | } 137 | 138 | // Fetch the next token. Dispatches on first character in the 139 | // stream, or first two characters when the first is a slash. 140 | if (inside == "\"" || inside == "'") 141 | return readString(inside); 142 | var ch = source.next(); 143 | if (inside == "/*") 144 | return readMultilineComment(ch); 145 | else if (ch == "\"" || ch == "'") 146 | return readString(ch); 147 | // with punctuation, the type of the token is the symbol itself 148 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 149 | return {type: ch, style: "js-punctuation"}; 150 | else if (ch == "0" && (source.equals("x") || source.equals("X"))) 151 | return readHexNumber(); 152 | else if (/[0-9]/.test(ch)) 153 | return readNumber(); 154 | else if (ch == "/"){ 155 | if (source.equals("*")) 156 | { source.next(); return readMultilineComment(ch); } 157 | else if (source.equals("/")) 158 | { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};} 159 | else if (regexp) 160 | return readRegexp(); 161 | else 162 | return readOperator(); 163 | } 164 | else if (isOperatorChar.test(ch)) 165 | return readOperator(); 166 | else 167 | return readWord(); 168 | } 169 | 170 | // The external interface to the tokenizer. 171 | return function(source, startState) { 172 | return tokenizer(source, startState || jsTokenState(false, true)); 173 | }; 174 | })(); 175 | -------------------------------------------------------------------------------- /chrome/content/codemirror/util.js: -------------------------------------------------------------------------------- 1 | /* A few useful utility functions. */ 2 | 3 | // Capture a method on an object. 4 | function method(obj, name) { 5 | return function() {obj[name].apply(obj, arguments);}; 6 | } 7 | 8 | // The value used to signal the end of a sequence in iterators. 9 | var StopIteration = {toString: function() {return "StopIteration"}}; 10 | 11 | // Apply a function to each element in a sequence. 12 | function forEach(iter, f) { 13 | if (iter.next) { 14 | try {while (true) f(iter.next());} 15 | catch (e) {if (e != StopIteration) throw e;} 16 | } 17 | else { 18 | for (var i = 0; i < iter.length; i++) 19 | f(iter[i]); 20 | } 21 | } 22 | 23 | // Map a function over a sequence, producing an array of results. 24 | function map(iter, f) { 25 | var accum = []; 26 | forEach(iter, function(val) {accum.push(f(val));}); 27 | return accum; 28 | } 29 | 30 | // Create a predicate function that tests a string againsts a given 31 | // regular expression. No longer used but might be used by 3rd party 32 | // parsers. 33 | function matcher(regexp){ 34 | return function(value){return regexp.test(value);}; 35 | } 36 | 37 | // Test whether a DOM node has a certain CSS class. 38 | function hasClass(element, className) { 39 | var classes = element.className; 40 | return classes && new RegExp("(^| )" + className + "($| )").test(classes); 41 | } 42 | function removeClass(element, className) { 43 | element.className = element.className.replace(new RegExp(" " + className + "\\b", "g"), ""); 44 | return element; 45 | } 46 | 47 | // Insert a DOM node after another node. 48 | function insertAfter(newNode, oldNode) { 49 | var parent = oldNode.parentNode; 50 | parent.insertBefore(newNode, oldNode.nextSibling); 51 | return newNode; 52 | } 53 | 54 | function removeElement(node) { 55 | if (node.parentNode) 56 | node.parentNode.removeChild(node); 57 | } 58 | 59 | function clearElement(node) { 60 | while (node.firstChild) 61 | node.removeChild(node.firstChild); 62 | } 63 | 64 | // Check whether a node is contained in another one. 65 | function isAncestor(node, child) { 66 | while (child = child.parentNode) { 67 | if (node == child) 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | // The non-breaking space character. 74 | var nbsp = "\u00a0"; 75 | var matching = {"{": "}", "[": "]", "(": ")", 76 | "}": "{", "]": "[", ")": "("}; 77 | 78 | // Standardize a few unportable event properties. 79 | function normalizeEvent(event) { 80 | if (!event.stopPropagation) { 81 | event.stopPropagation = function() {this.cancelBubble = true;}; 82 | event.preventDefault = function() {this.returnValue = false;}; 83 | } 84 | if (!event.stop) { 85 | event.stop = function() { 86 | this.stopPropagation(); 87 | this.preventDefault(); 88 | }; 89 | } 90 | 91 | if (event.type == "keypress") { 92 | event.code = (event.charCode == null) ? event.keyCode : event.charCode; 93 | event.character = String.fromCharCode(event.code); 94 | } 95 | return event; 96 | } 97 | 98 | // Portably register event handlers. 99 | function addEventHandler(node, type, handler, removeFunc) { 100 | function wrapHandler(event) { 101 | handler(normalizeEvent(event || window.event)); 102 | } 103 | if (typeof node.addEventListener == "function") { 104 | node.addEventListener(type, wrapHandler, false); 105 | if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);}; 106 | } 107 | else { 108 | node.attachEvent("on" + type, wrapHandler); 109 | if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);}; 110 | } 111 | } 112 | 113 | function nodeText(node) { 114 | return node.textContent || node.innerText || node.nodeValue || ""; 115 | } 116 | 117 | function nodeTop(node) { 118 | var top = 0; 119 | while (node.offsetParent) { 120 | top += node.offsetTop; 121 | node = node.offsetParent; 122 | } 123 | return top; 124 | } 125 | 126 | function isBR(node) { 127 | var nn = node.nodeName; 128 | return nn == "BR" || nn == "br"; 129 | } 130 | function isSpan(node) { 131 | var nn = node.nodeName; 132 | return nn == "SPAN" || nn == "span"; 133 | } 134 | -------------------------------------------------------------------------------- /chrome/content/firerainbow.js: -------------------------------------------------------------------------------- 1 | FBL.ns(function() { 2 | with (FBL) { 3 | // some people reported that rainbow was initialised twice 4 | // see http://getsatisfaction.com/xrefresh/topics/too_many_recursions_problem_with_rainbow 5 | // this is a hack how to prevent it 6 | if (!FBL.rainbowInitialised) { 7 | FBL.rainbowInitialised = true; 8 | 9 | const MAX_LINE_LENGTH = 500; 10 | 11 | const Cc = Components.classes; 12 | const Ci = Components.interfaces; 13 | 14 | // test for feature added in r686 (http://code.google.com/p/fbug/source/detail?r=686) 15 | // note: previous rainbow did break firebug without this test 16 | var cssPanelAvailable = !!Firebug.CSSStyleSheetPanel; 17 | if (!cssPanelAvailable) { 18 | var consoleService = Cc['@mozilla.org/consoleservice;1'].getService(Components.interfaces.nsIConsoleService); 19 | consoleService.logStringMessage("FireRainbow requires Firebug 1.3+ (your have "+Firebug.getVersion()+")."); 20 | consoleService.logStringMessage('Please update your Firebug extension to the latest version (http://getfirebug.com).'); 21 | } else { 22 | const nsIPrefBranch = Ci.nsIPrefBranch; 23 | const nsIPrefBranch2 = Ci.nsIPrefBranch2; 24 | 25 | const rainbowPrefService = Cc["@mozilla.org/preferences-service;1"]; 26 | const rainbowPrefs = rainbowPrefService.getService(nsIPrefBranch2); 27 | 28 | const rainbowWebsite = "http://firerainbow.binaryage.com"; 29 | const rainbowPrefDomain = "extensions.rainbow"; 30 | 31 | const currentCodeVersion = 2; 32 | 33 | if (Firebug.TraceModule) { 34 | Firebug.TraceModule.DBG_FIRERAINBOW = false; 35 | var type = rainbowPrefs.getPrefType('extensions.firebug.DBG_FIRERAINBOW'); 36 | if (type!=nsIPrefBranch.PREF_BOOL) try { 37 | rainbowPrefs.setBoolPref('extensions.firebug.DBG_FIRERAINBOW', false); 38 | } catch(e) {} 39 | } 40 | 41 | var dbg = function() { 42 | if (FBTrace && FBTrace.DBG_FIRERAINBOW) { 43 | FBTrace.sysout.apply(this, arguments); 44 | } 45 | }; 46 | 47 | var buildToken = function(style, val) { 48 | return '' + escapeForSourceLine(val) + ''; 49 | }; 50 | 51 | var processTokenStream = function(stream) { 52 | // stream is array of pairs 53 | // apply coloring to the line 54 | var pieces = []; 55 | for(var i=0; i 0) { 63 | var space = new Array(Firebug.replaceTabs + 1).join(" "); 64 | output = output.replace(/\t/g, space); 65 | } 66 | 67 | return output; 68 | }; 69 | 70 | //////////////////////////////////////////////////////////////////////// 71 | // Firebug.FireRainbowExtension 72 | // 73 | Firebug.FireRainbowExtension = extend(Firebug.Extension, { 74 | // this is called whenever script viewport is about to be rendered 75 | onApplyDecorator: function(sourceBox) { 76 | // patch sourcebox render functionality 77 | if (!sourceBox.rainbowPatched) { 78 | sourceBox.rainbowPatched = true; 79 | 80 | if (sourceBox.getLineAsHTML) { // Firebug 1.3 and 1.4 path 81 | // 1. I use Firebug.Extension.onApplyDecorator mechanism to get called 82 | // for every sourceBox which is about be displayed 83 | // 2. first time a source box is seen, I patch sourceBox.getLineAsHTML 84 | // with my "slightly smarter" version 85 | // 3. for given sourceBox I trigger "daemon process", which starts 86 | // coloring off-screen buffer of all lines (not just visible ones) => 87 | // sourceBox.colorizedLines 88 | // 89 | // Every time Firebug needs to render lines, it calls getLineAsHTML on 90 | // sourceBox, so it calls my version of that function and I return 91 | // colorized line in case I have it ready. 92 | // 93 | // Note: In the case daemon just crossed actual viewport, I'm trying to 94 | // force source panel to refresh it's content calling 95 | // scriptPanel.reView(sourceBox);. 96 | // This is tricky, because reView has implemented several layers of 97 | // caching, continuously being added with newer versions, which makes my 98 | // life harder :-) 99 | // If anyone knows better a function to call, I would be happy to make 100 | // this more robust. 101 | if (!sourceBox._rainbowOriginalGetLineAsHTML) { 102 | sourceBox._rainbowOriginalGetLineAsHTML = sourceBox.getLineAsHTML; 103 | sourceBox.getLineAsHTML = function(lineNo) { 104 | if (this.colorizedLines) { 105 | var line = this.colorizedLines[lineNo]; 106 | if (line!==undefined) return line; 107 | } 108 | return this._rainbowOriginalGetLineAsHTML(lineNo); 109 | }; 110 | } 111 | } 112 | 113 | if (sourceBox.decorator) { // Firebug 1.5 path 114 | // here I patch getLineHTML and using similar technique like for Firebug 1.3 and 1.4 115 | // when firebug needs to render lines it asks getLineHTML to provide HTML version of every line 116 | // this is quite fast and reasonably smooth when scrolling 117 | 118 | // Note: In Firebug 1.5 call to scriptPanel.reView(sourceBox, true) invalidates cache, so it is guaranteed to redraw the view 119 | if (!sourceBox.decorator._rainbowOriginalGetLineHTML) { 120 | sourceBox.decorator._rainbowOriginalGetLineHTML = sourceBox.decorator.getLineHTML; 121 | sourceBox.decorator.getLineHTML = function(sourceBox, lineNo) { 122 | if (sourceBox.colorizedLines) { 123 | var line = sourceBox.colorizedLines[lineNo-1]; 124 | if (line!==undefined) return line; 125 | } 126 | return this._rainbowOriginalGetLineHTML(sourceBox, lineNo); 127 | }; 128 | } 129 | } 130 | } 131 | // prevent recursion in case we call reView 132 | if (sourceBox.preventRainbowRecursion) { 133 | sourceBox.preventRainbowRecursion = undefined; 134 | return; 135 | } 136 | // start coloring (if not already in progress or done) 137 | Firebug.FireRainbowModule.colorizeSourceBox(sourceBox); 138 | } 139 | }); 140 | 141 | //////////////////////////////////////////////////////////////////////// 142 | // Firebug.FireRainbowModule 143 | // 144 | Firebug.FireRainbowModule = extend(Firebug.Module, { 145 | valid: false, 146 | pings: 0, 147 | styleLibrary: {}, 148 | // there is a copy of this in defaults/preferences/firerainbow.js !!! 149 | defaultTheme: ".panelNode-script{background-color:#FFFFFF;color:black;} .sourceRow.hovered{background-color:#EEEEEE;} .sourceLine{background:#EEEEEE none no-repeat scroll 2px 0;border-bottom:1px solid #EEEEEE;border-right:1px solid #CCCCCC;color:#888888;} .sourceLine:hover{text-decoration:none;} .scriptTooltip{background:LightYellow none repeat scroll 0 0;border:1px solid #CBE087;color:#000000;} .sourceRow[exeline=\"true\"]{background-color:lightgoldenrodyellow;outline-color:#D9D9B6;outline-style:solid;outline-width:1px;} .xml-text{color:black;} .whitespace{color:black;} .xml-punctuation{color:gray;} .xml-tagname{color:blue;} .xml-attname{color:darkred;} .xml-attribute{color:darkgreen;} .css-at{color:darkred;} .css-string{color:red;} .css-punctuation{color:midnightblue;} .js-keyword{color:blue;} .js-variable{color:black;} .js-operator{color:black;} .js-punctuation{color:darkBlue;} .js-variabledef{color:darkslategray;} .js-localvariable{color:darkslateBlue;} .js-property{color:teal;} .js-string{color:darkgreen;} .js-atom{color:saddleBrown;} .xml-comment{color:gray;} .css-identifier{color:midnightBlue;} .css-select-op{color:cadetblue;} .css-unit{color:orangered;} .css-value{color:black;} .css-colorcode{color:magenta;} .js-comment{color:gray;} .js-regexp{color:magenta;} .xml-entity{color:darkgoldenrod;} .xml-error{color:orangered;} .css-comment{color:gray;}", 150 | 151 | ///////////////////////////////////////////////////////////////////////////////////////// 152 | initialize: function() { 153 | return Firebug.Module.initialize.apply(this, arguments); 154 | }, 155 | ///////////////////////////////////////////////////////////////////////////////////////// 156 | showPanel: function(browser, panel) { 157 | if (!this.valid) return; 158 | dbg("Rainbow: showPanel", panel); 159 | var isScriptPanel = panel && panel.name == "script"; 160 | this.actualScriptPanel = isScriptPanel?panel:undefined; 161 | }, 162 | ///////////////////////////////////////////////////////////////////////////////////////// 163 | initContext: function(context) { 164 | dbg("Rainbow: initContext", context); 165 | Firebug.Module.initContext.apply(this, arguments); 166 | this.hookPanel(context); 167 | this.valid = true; 168 | }, 169 | ///////////////////////////////////////////////////////////////////////////////////////// 170 | reattachContext: function(browser, context) { 171 | Firebug.Module.reattachContext.apply(this, arguments); 172 | this.hookPanel(context); 173 | }, 174 | ///////////////////////////////////////////////////////////////////////////////////////// 175 | // convert old code to be compatible with current rainbow 176 | convertOldCode: function(code, version) { 177 | switch (version) { 178 | case 1: return code.replace(/\.(\w+)\s*\{/g, ".js-$1 {"); // conversion for mixed html coloring 179 | } 180 | return code; 181 | }, 182 | ///////////////////////////////////////////////////////////////////////////////////////// 183 | getCodeVersion: function(code) { 184 | var vc = code.match(/\/\* version:(.*) \*\//); 185 | if (!vc) return 1; 186 | return parseInt(vc[1], 10); 187 | }, 188 | colorizeSourceBox: function(sourceBox) { 189 | dbg("Rainbow: colorizeSourceBox", sourceBox); 190 | this.pingDaemon(sourceBox); 191 | }, 192 | ///////////////////////////////////////////////////////////////////////////////////////// 193 | hookPanel: function(context) { 194 | dbg("Rainbow: hookPanel", context); 195 | var chrome = context ? context.chrome : FirebugChrome; 196 | var code = this.getPref('coloring'); 197 | var version = this.getCodeVersion(code); 198 | if (version= sourceBox.lines.length) { 383 | return finish(); 384 | } 385 | 386 | // extract line code from node 387 | // note: \n is important to simulate multi line text in stream (for example multi-line comments depend on this) 388 | nextLine = sourceBox.lines[sourceBox.lineToBeColorized]+"\n"; 389 | 390 | sourceBox.parsedLine = []; 391 | sourceBox.hasLine = true; 392 | } 393 | 394 | codemirror.forEach(sourceBox.parser, 395 | function(token) { 396 | // colorize token 397 | var val = token.value; 398 | sourceBox.parsedLine.push([token.style, val]); 399 | that.styleLibrary[token.style] = true; 400 | if (--tokenQuota==0) { 401 | throw StopIteration; 402 | } 403 | } 404 | ); 405 | 406 | if (!tokenQuota) { 407 | return; 408 | } 409 | 410 | sourceBox.colorizedLines.push(processTokenStream(sourceBox.parsedLine)); 411 | 412 | if (sourceBox.lineToBeColorized==sourceBox.lastViewableLine) { 413 | // just crossed actual view, force refresh! 414 | refresh(); 415 | startLine = null; 416 | } 417 | 418 | // move for next line 419 | sourceBox.lineToBeColorized++; 420 | sourceBox.hasLine = false; 421 | } 422 | } catch (ex) { 423 | dbg("Rainbow: exception", ex); 424 | // stop daemon in this exceptional case 425 | that.stopDaemon(); 426 | sourceBox.colorized = true; 427 | sourceBox.colorizationFailed = true; 428 | // free up memory 429 | sourceBox.parser = undefined; 430 | return; 431 | } 432 | }, 433 | daemonInterval); 434 | }, 435 | ///////////////////////////////////////////////////////////////////////////////////////// 436 | pingDaemon: function(sourceBox) { 437 | if (!this.valid) return; 438 | 439 | // trivial implementation of buffered deferred triggering of daemon 440 | this.pings++; 441 | var pingMarker = this.pings; 442 | var that = this; 443 | setTimeout(function(){ 444 | if (that.pings!=pingMarker) return; 445 | that.startDaemon(sourceBox); 446 | }, 200); 447 | }, 448 | ///////////////////////////////////////////////////////////////////////////////////////// 449 | // initializes syntax coloring helpers for panel 450 | initSyntaxColoring: function(panelBar) { 451 | // here we append into head element 452 | // this style element we will use to apply coloring rules to all script boxes in the panel 453 | if (this.lookupStyleElement(panelBar)) return; // already done 454 | 455 | var browser = panelBar.browser; 456 | var doc = browser.contentDocument; 457 | 458 | var styleElement = doc.createElement("style"); 459 | styleElement.setAttribute("id", "rainbow-style-sheet"); 460 | styleElement.setAttribute("type", "text/css"); 461 | styleElement.appendChild(doc.createTextNode('/* Syntax coloring */')); 462 | 463 | var headElement; 464 | var headElementList = doc.getElementsByTagName("head"); 465 | if (headElementList.length) headElement = headElementList[0]; else headElement = doc.documentElement; 466 | headElement.appendChild(styleElement); 467 | }, 468 | ///////////////////////////////////////////////////////////////////////////////////////// 469 | // returns our rainbow-style-sheet element from given panel 470 | lookupStyleElement: function(panelBar) { 471 | var browser = panelBar.browser; 472 | var doc = browser.contentDocument; 473 | var styleElement = doc.getElementById('rainbow-style-sheet'); 474 | return styleElement; 475 | }, 476 | ///////////////////////////////////////////////////////////////////////////////////////// 477 | // applies new coloring rules to given panel 478 | applySyntaxColoring: function(code, panelBar) { 479 | var styleElement = this.lookupStyleElement(panelBar); 480 | if (!styleElement) return; 481 | styleElement.innerHTML = ''; 482 | var browser = panelBar.browser; 483 | var doc = browser.contentDocument; 484 | styleElement.appendChild(doc.createTextNode(code)); 485 | }, 486 | ///////////////////////////////////////////////////////////////////////////////////////// 487 | // serializes CSS rules and stores them into coloring property (save) 488 | saveSyntaxColoring: function(rules) { 489 | var code = rules; 490 | if (typeof code != 'string') { 491 | var s = []; 492 | for (var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 11 | 23 | 24 | 25 |
26 | 27 |

I'll tell you a secret ...

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /support/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryage/firerainbow/1cefdabc94422778afc07ca3122a052c307477ca/support/screenshot.png -------------------------------------------------------------------------------- /themes/active4d.css: -------------------------------------------------------------------------------- 1 | /* Active4D, converted from TextMate theme (Active4D.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #FFFFFF; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #BAD6FD; 16 | } 17 | 18 | .js-string { 19 | color: #666666; 20 | } 21 | 22 | .js-atom { 23 | color: #A8017E; 24 | } 25 | 26 | .js-variable { 27 | color: #A535AE; 28 | } 29 | 30 | .js-variable { 31 | font-weight: bold; 32 | color: #0053FF; 33 | } 34 | 35 | .js-keyword { 36 | font-weight: bold; 37 | color: #006699; 38 | } 39 | 40 | .xml-tagname { 41 | color: #7A7A7A; 42 | } 43 | 44 | .xml-tagname { 45 | color: #016CFF; 46 | } 47 | 48 | .xml-attname { 49 | color: #963DFF; 50 | } 51 | -------------------------------------------------------------------------------- /themes/all_hallows_eve.css: -------------------------------------------------------------------------------- 1 | /* All Hallows Eve by David Heinemeier Hansson, converted from TextMate theme (All Hallows Eve.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #000000; 6 | font-family: Monaco, Courier New; 7 | color: #FFFFFF; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #333300; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #73597E; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #9933CC; 20 | } 21 | 22 | .js-atom { 23 | color: #3387CC; 24 | } 25 | 26 | .js-keyword { 27 | color: #CC7833; 28 | } 29 | 30 | .js-string { 31 | color: #66CC33; 32 | } 33 | 34 | .js-regexp { 35 | color: #CCCC33; 36 | } -------------------------------------------------------------------------------- /themes/amy.css: -------------------------------------------------------------------------------- 1 | /* Amy by William D. Neumann, converted from TextMate theme (Amy.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #200020; 6 | font-family: Monaco, Courier New; 7 | color: #D0D0FF; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #800000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #800000; 16 | } 17 | 18 | .js-string { 19 | color: #999999; 20 | } 21 | 22 | .js-variable { 23 | color: #707090; 24 | } 25 | 26 | .js-atom { 27 | color: #7090B0; 28 | } 29 | 30 | .js-variabledef { 31 | color: #008080; 32 | } 33 | 34 | .js-keyword { 35 | color: #A080FF; 36 | } 37 | 38 | .xml-tagname { 39 | color: #009090; 40 | } 41 | 42 | .panelNode-script { 43 | font-size: 11px; 44 | background-color: #200020; 45 | font-family: Monaco, Courier New; 46 | } 47 | -------------------------------------------------------------------------------- /themes/blackboard.css: -------------------------------------------------------------------------------- 1 | /* Blackboard by Domenico Carbotta, converted from TextMate theme (Blackboard.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #0C1021; 6 | font-family: Monaco, Courier New; 7 | color: #F8F8F8; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #FFFFFF; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #253B76; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #AEAEAE; 20 | } 21 | 22 | .js-atom { 23 | color: #D8FA3C; 24 | } 25 | 26 | .js-keyword { 27 | color: #FBDE2D; 28 | } 29 | 30 | .js-string { 31 | color: #61CE3C; 32 | } 33 | 34 | .xml-tagname { 35 | color: #7F90AA; 36 | } 37 | -------------------------------------------------------------------------------- /themes/brilliance_black.css: -------------------------------------------------------------------------------- 1 | /* Brilliance Black by Thomas Aylott, converted from TextMate theme (Brilliance Black.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #0D0D0D; 6 | font-family: Monaco, Courier New; 7 | color: #EEEEEE; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000080; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #0010B4; 16 | } 17 | 18 | .js-regexp { 19 | background-color: #438000; 20 | color: #FFF800; 21 | } 22 | 23 | .js-atom { 24 | color: #C6FF00; 25 | } 26 | 27 | .js-variable { 28 | color: #07FF00; 29 | } 30 | 31 | .js-variabledef { 32 | color: #31A6FF; 33 | } 34 | 35 | .xml-tagname { 36 | color: #333333; 37 | } 38 | 39 | .xml-tagname { 40 | color: #FFFFFF; 41 | } 42 | 43 | .xml-attname { 44 | color: #FFFFFF; 45 | } 46 | 47 | .xml-attname { 48 | color: #4F00FF; 49 | } 50 | 51 | .xml-attname { 52 | color: #7900FF; 53 | } 54 | 55 | .xml-attname { 56 | color: #F800FF; 57 | } 58 | 59 | .xml-attname { 60 | color: #FF0086; 61 | } 62 | 63 | .xml-attname { 64 | font-weight: bold; 65 | color: #FF7900; 66 | } 67 | 68 | .xml-attname { 69 | color: #C25A00; 70 | } 71 | -------------------------------------------------------------------------------- /themes/brilliance_dull.css: -------------------------------------------------------------------------------- 1 | /* Brilliance Dull by Thomas Aylott, converted from TextMate theme (Brilliance Dull.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #050505; 6 | font-family: Monaco, Courier New; 7 | color: #CDCDCD; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000080; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #2E2EE6; 16 | } 17 | 18 | .js-regexp { 19 | background-color: #438000; 20 | color: #A6A458; 21 | } 22 | 23 | .js-atom { 24 | color: #95A658; 25 | } 26 | 27 | .js-variable { 28 | color: #59A559; 29 | } 30 | 31 | .js-variabledef { 32 | color: #5E6B6B; 33 | } 34 | 35 | .js-variabledef { 36 | color: #5780A6; 37 | } 38 | 39 | .js-keyword { 40 | color: #A459A5; 41 | } 42 | 43 | .xml-tagname { 44 | color: #333333; 45 | } 46 | 47 | .xml-attname { 48 | background-color: #800004; 49 | color: #A7595A; 50 | } 51 | 52 | .xml-tagname { 53 | color: #FFFFFF; 54 | } 55 | 56 | .xml-attname { 57 | color: #FFFFFF; 58 | } 59 | 60 | .xml-attname { 61 | color: #7C58A5; 62 | } 63 | 64 | .xml-attname { 65 | color: #A459A5; 66 | } 67 | 68 | .xml-attname { 69 | color: #A75980; 70 | } 71 | 72 | .xml-attname { 73 | font-weight: bold; 74 | color: #A77D58; 75 | } 76 | 77 | .js-comment, .xml-comment, .css-comment { 78 | color: #333333; 79 | } 80 | -------------------------------------------------------------------------------- /themes/cobalt.css: -------------------------------------------------------------------------------- 1 | /* Cobalt by Jacob Rus, converted from TextMate theme (Cobalt.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #002240; 6 | font-family: Monaco, Courier New; 7 | color: #FFFFFF; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #B36539; 16 | } 17 | 18 | .js-atom { 19 | color: #FF628C; 20 | } 21 | 22 | .js-keyword { 23 | color: #FF9D00; 24 | } 25 | 26 | .js-comment, .xml-comment, .css-comment { 27 | color: #0088FF; 28 | } 29 | 30 | .js-variable { 31 | color: #CCCCCC; 32 | } 33 | 34 | .js-variabledef { 35 | color: #FF80E1; 36 | } 37 | 38 | .js-regexp { 39 | color: #80FFC2; 40 | } 41 | 42 | .xml-tagname { 43 | color: #9EFFFF; 44 | } 45 | 46 | .css-identifier { 47 | color: #9EFFFF; 48 | } 49 | 50 | .xml-attname { 51 | color: #FFB454; 52 | } 53 | 54 | .xml-attname { 55 | color: #5FE461; 56 | } 57 | 58 | .css-colorcode { 59 | color: #9DF39F; 60 | } 61 | 62 | .css-value { 63 | color: #F6F080; 64 | } 65 | 66 | .css-at { 67 | color: #F6AA11; 68 | } 69 | -------------------------------------------------------------------------------- /themes/codemirror.css: -------------------------------------------------------------------------------- 1 | .panelNode-script { 2 | font-family: Courier New; 3 | font-size: 9pt; 4 | overflow: hidden; 5 | } 6 | .js-keyword { 7 | color: #770088; 8 | } 9 | .js-atom { 10 | color: #228811; 11 | } 12 | .js-variable { 13 | color: black; 14 | } 15 | .js-variabledef { 16 | color: #0000FF; 17 | } 18 | .js-localvariable { 19 | color: #004499; 20 | } 21 | .js-property { 22 | color: black; 23 | } 24 | .js-comment { 25 | color: #AA7700; 26 | } 27 | .js-string { 28 | color: #AA2222; 29 | } 30 | -------------------------------------------------------------------------------- /themes/cowtown.css: -------------------------------------------------------------------------------- 1 | /* cowtown by psylux */ 2 | .panelNode-script { 3 | background:#3F2B18 none repeat scroll 0 0; 4 | color:#7C4B00; 5 | font-family:consolas,Monospaced,Courier New; 6 | font-size:12px; 7 | overflow:hidden; 8 | } 9 | .js-keyword { 10 | color:#FF9D00; 11 | } 12 | .js-atom { 13 | color:#FFFF33; 14 | } 15 | .js-variable { 16 | color:#B0B9F2; 17 | } 18 | .js-variabledef { 19 | color:#FFFFFF; 20 | } 21 | .js-localvariable { 22 | color:#DDD87B; 23 | } 24 | .js-property { 25 | color:#44FFFF; 26 | } 27 | .js-comment { 28 | color:#9A4690; 29 | } 30 | .js-string { 31 | color:#7EDC0F; 32 | } 33 | .js-regexp { 34 | color:#FFA1AC; 35 | } 36 | .js-punctuation { 37 | color:#E1EFFF; 38 | } 39 | .js-operator { 40 | color:#FF6633; 41 | } 42 | .js-whitespace { 43 | color:#EEFFEE; 44 | } 45 | -------------------------------------------------------------------------------- /themes/dawn.css: -------------------------------------------------------------------------------- 1 | /* Dawn by David Powers, converted from TextMate theme (Dawn.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #F9F9F9; 6 | font-family: Monaco, Courier New; 7 | color: #080808; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #2463B4; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #275FFF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #5A525F; 20 | } 21 | 22 | .js-atom { 23 | font-weight: bold; 24 | color: #811F24; 25 | } 26 | 27 | .js-keyword { 28 | color: #794938; 29 | } 30 | 31 | .js-variable { 32 | color: #234A97; 33 | } 34 | 35 | .js-regexp { 36 | color: #CF5628; 37 | } 38 | -------------------------------------------------------------------------------- /themes/eclipse.css: -------------------------------------------------------------------------------- 1 | .panelNode-script { 2 | font-family: Courier New; 3 | font-size: 9pt; 4 | overflow: hidden; 5 | } 6 | .js-keyword { 7 | color: #7F0055; 8 | } 9 | .js-atom { 10 | color: #7F0055; 11 | } 12 | .js-variable { 13 | color: black; 14 | } 15 | .js-variabledef { 16 | color: black; 17 | } 18 | .js-localvariable { 19 | color: black; 20 | } 21 | .js-property { 22 | color: black; 23 | } 24 | .js-comment { 25 | color: #3F5FBF; 26 | } 27 | .js-string { 28 | color: #8E00FF; 29 | } 30 | -------------------------------------------------------------------------------- /themes/eiffel.css: -------------------------------------------------------------------------------- 1 | /* Eiffel by Ian Joyner, converted from TextMate theme (Eiffel.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #FFFFFF; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #C3DCFF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #00B418; 20 | } 21 | 22 | .js-variable { 23 | color: #0206FF; 24 | } 25 | 26 | .js-keyword { 27 | font-weight: bold; 28 | color: #0100B6; 29 | } 30 | 31 | .js-atom { 32 | color: #CD0000; 33 | } 34 | 35 | .js-atom { 36 | color: #C5060B; 37 | } 38 | 39 | .js-variable { 40 | color: #585CF6; 41 | } 42 | 43 | .js-string { 44 | color: #D80800; 45 | } 46 | 47 | .js-operator { 48 | color: #687687; 49 | } 50 | 51 | .xml-tagname { 52 | color: #1C02FF; 53 | } 54 | 55 | .xml-tagname { 56 | font-weight: bold; 57 | } 58 | -------------------------------------------------------------------------------- /themes/espresso_libre.css: -------------------------------------------------------------------------------- 1 | /* Espresso Libre by Chris Thomas, converted from TextMate theme (Espresso Libre.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #2A211C; 6 | font-family: Monaco, Courier New; 7 | color: #BDAE9D; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #3A312C; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #C3DCFF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #0066FF; 20 | } 21 | 22 | .js-keyword { 23 | font-weight: bold; 24 | color: #43A8ED; 25 | } 26 | 27 | .js-atom { 28 | color: #44AA43; 29 | } 30 | 31 | .js-atom { 32 | font-weight: bold; 33 | color: #C5656B; 34 | } 35 | 36 | .js-variable { 37 | font-weight: bold; 38 | color: #585CF6; 39 | } 40 | 41 | .js-variabledef { 42 | color: #318495; 43 | } 44 | 45 | .js-string { 46 | color: #049B0A; 47 | } 48 | 49 | .js-operator { 50 | color: #687687; 51 | } 52 | 53 | .xml-processing { 54 | color: #8F7E65; 55 | } 56 | 57 | .xml-processing { 58 | color: #888888; 59 | } 60 | 61 | .xml-tagname { 62 | color: #43A8ED; 63 | } 64 | 65 | .xml-tagname { 66 | font-weight: bold; 67 | } 68 | -------------------------------------------------------------------------------- /themes/idle.css: -------------------------------------------------------------------------------- 1 | /* IDLE by Domenico Carbotta, converted from TextMate theme (IDLE.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #FFFFFF; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #BAD6FD; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #919191; 20 | } 21 | 22 | .js-string { 23 | color: #00A33F; 24 | } 25 | 26 | .js-variable { 27 | color: #A535AE; 28 | } 29 | 30 | .js-keyword { 31 | color: #FF5600; 32 | } 33 | -------------------------------------------------------------------------------- /themes/iplastic.css: -------------------------------------------------------------------------------- 1 | /* iPlastic by Jeroen van der Ham, converted from TextMate theme (iPlastic.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #EEEEEE; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #BAD6FD; 16 | } 17 | 18 | .js-string { 19 | color: #009933; 20 | } 21 | 22 | .js-atom { 23 | color: #0066FF; 24 | } 25 | 26 | .js-regexp { 27 | color: #FF0080; 28 | } 29 | 30 | .js-keyword { 31 | color: #0000FF; 32 | } 33 | 34 | .js-variable { 35 | color: #9700CC; 36 | } 37 | 38 | .js-comment, .xml-comment, .css-comment { 39 | color: #0066FF; 40 | } 41 | 42 | .xml-tagname { 43 | color: #0033CC; 44 | } 45 | 46 | .js-atom { 47 | color: #6782D3; 48 | } 49 | 50 | .xml-processing { 51 | color: #333333; 52 | } 53 | 54 | .xml-attname { 55 | color: #3366CC; 56 | } 57 | 58 | .xml-tagname { 59 | font-weight: bold; 60 | } 61 | -------------------------------------------------------------------------------- /themes/ir_black.css: -------------------------------------------------------------------------------- 1 | /* IR_Black, converted from TextMate theme (IR_Black.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #141414; 6 | font-family: Monaco, Courier New; 7 | color: #EDEDED; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #FFFFFF; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #333333; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #7C7C7C; 20 | } 21 | 22 | .js-keyword { 23 | color: #96CBFE; 24 | } 25 | 26 | .js-atom { 27 | color: #99CC99; 28 | } 29 | 30 | .js-string { 31 | font-weight: bold; 32 | color: #A8FF60; 33 | } 34 | 35 | .js-atom { 36 | font-weight: bold; 37 | color: #FF73FD; 38 | } 39 | 40 | .js-variable { 41 | color: #C6C5FE; 42 | } 43 | 44 | .js-regexp { 45 | color: #E9C062; 46 | } 47 | 48 | .xml-tagname { 49 | font-weight: bold; 50 | color: #96CBFE; 51 | } 52 | 53 | .xml-attname { 54 | color: #96CBFE; 55 | } 56 | 57 | .xml-attname { 58 | color: #FFD7B1; 59 | } 60 | 61 | .xml-attname { 62 | color: #E18964; 63 | } 64 | 65 | .css-identifier { 66 | color: #96CBFE; 67 | } 68 | 69 | .xml-attname { 70 | color: #8F9D6A; 71 | } 72 | 73 | .xml-attname { 74 | color: #8B98AB; 75 | } 76 | 77 | .xml-attname { 78 | color: #62B1FE; 79 | } 80 | 81 | .css-colorcode { 82 | color: #EDEDED; 83 | } 84 | 85 | .css-value { 86 | color: #F9EE98; 87 | } 88 | 89 | .css-at { 90 | color: #8693A5; 91 | } 92 | -------------------------------------------------------------------------------- /themes/lazy.css: -------------------------------------------------------------------------------- 1 | /* LAZY by Domenico Carbotta, converted from TextMate theme (LAZY.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #FFFFFF; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #EFFCA6; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #E3FC8D; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #8C868F; 20 | } 21 | 22 | .js-atom { 23 | color: #3B5BB5; 24 | } 25 | 26 | .js-keyword { 27 | color: #FF7800; 28 | } 29 | 30 | .js-string { 31 | color: #409B1C; 32 | } 33 | 34 | .xml-tagname { 35 | color: #3A4A64; 36 | } 37 | -------------------------------------------------------------------------------- /themes/mac_classic.css: -------------------------------------------------------------------------------- 1 | /* Mac Classic by Chris Thomas, converted from TextMate theme (Mac Classic.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #FFFFFF; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #4D97FF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #808080; 20 | } 21 | 22 | .js-keyword { 23 | color: #0000FF; 24 | } 25 | 26 | .js-atom { 27 | color: #0000CD; 28 | } 29 | 30 | .js-atom { 31 | color: #C5060B; 32 | } 33 | 34 | .js-variable { 35 | color: #585CF6; 36 | } 37 | 38 | .js-variabledef { 39 | color: #318495; 40 | } 41 | 42 | .js-string { 43 | color: #036A07; 44 | } 45 | 46 | .js-operator { 47 | color: #687687; 48 | } 49 | 50 | .xml-processing { 51 | color: #68685B; 52 | } 53 | 54 | .xml-processing { 55 | color: #888888; 56 | } 57 | 58 | .xml-tagname { 59 | color: #0000FF; 60 | } 61 | 62 | .xml-tagname { 63 | color: #0000FF; 64 | } 65 | 66 | .xml-attname { 67 | color: #9C0202; 68 | } 69 | -------------------------------------------------------------------------------- /themes/magicwb_amiga.css: -------------------------------------------------------------------------------- 1 | /* MagicWB (Amiga) by Allan Odgaard, converted from TextMate theme (MagicWB (Amiga).tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #969696; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #B1B1B1; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #8D2E75; 20 | } 21 | 22 | .js-string { 23 | background-color: #FF0000; 24 | color: #FFFFFF; 25 | } 26 | 27 | .js-atom { 28 | color: #FFFFFF; 29 | } 30 | 31 | .js-variable { 32 | font-weight: bold; 33 | color: #FFA995; 34 | } 35 | 36 | .js-variabledef { 37 | color: #FFA995; 38 | } 39 | 40 | .js-keyword { 41 | font-weight: bold; 42 | } 43 | 44 | .xml-attname { 45 | color: #3A68A3; 46 | } 47 | -------------------------------------------------------------------------------- /themes/pastels_on_dark.css: -------------------------------------------------------------------------------- 1 | /* Pastels on Dark by Mats Persson, converted from TextMate theme (Pastels on Dark.tmTheme) */ 2 | 3 | /* 4 | CHANGELOG 5 | 6 | 1) 2010-06-18 cbalz Fixed issue where stopped line on breakpoint has background color that makes text almost invisible. 7 | 8 | */ 9 | 10 | .panelNode-script { 11 | font-size: 11px; 12 | background-color: #211E1E; 13 | font-family: Monaco, Courier New; 14 | color: #DADADA; 15 | } 16 | 17 | .sourceRow.hovered { 18 | background-color: #353030; 19 | } 20 | 21 | .sourceRow[exeLine="true"] { 22 | background-color: #73597E; 23 | } 24 | 25 | .js-comment, .xml-comment, .css-comment { 26 | color: #555555; 27 | } 28 | 29 | .js-string { 30 | color: #AD9361; 31 | } 32 | 33 | .js-atom { 34 | color: #CCCCCC; 35 | } 36 | 37 | .js-keyword { 38 | color: #A1A1FF; 39 | } 40 | 41 | .js-regexp { 42 | color: #666666; 43 | } 44 | 45 | .js-variable { 46 | color: #C1C144; 47 | } 48 | 49 | .js-atom { 50 | color: #6782D3; 51 | } 52 | 53 | .js-variable { 54 | font-weight: bold; 55 | color: #DE8E30; 56 | } 57 | 58 | .xml-attname { 59 | color: #9B456F; 60 | } 61 | 62 | .xml-processing { 63 | color: #68685B; 64 | } 65 | 66 | .xml-processing { 67 | color: #888888; 68 | } 69 | 70 | .xml-attname { 71 | color: #9B456F; 72 | } 73 | 74 | .xml-attname { 75 | color: #EC9E00; 76 | } 77 | 78 | .xml-attname { 79 | color: #EDCA06; 80 | } 81 | 82 | .xml-attname { 83 | color: #2E759C; 84 | } 85 | 86 | .css-value { 87 | color: #9B2E4D; 88 | } 89 | 90 | .css-colorcode { 91 | color: #E1C96B; 92 | } 93 | 94 | .sourceRow[exe_line="true"] { 95 | outline: 1px solid #D9D9B6; margin-right: 1px; background-color: purple; 96 | } 97 | -------------------------------------------------------------------------------- /themes/rainbow.css: -------------------------------------------------------------------------------- 1 | .panelNode-script { 2 | background-color: #FFFFFF; 3 | color: black; 4 | font-family: Monaco,Monospace,Courier New !important; 5 | font-size: 11px; 6 | } 7 | .sourceRow.hovered { 8 | background-color: #EEEEEE; 9 | } 10 | .sourceLine { 11 | background: #EEEEEE none no-repeat scroll 2px 0; 12 | border-bottom: 1px solid #EEEEEE; 13 | border-right: 1px solid #CCCCCC; 14 | color: #888888; 15 | } 16 | .sourceLine:hover { 17 | text-decoration: none; 18 | } 19 | .scriptTooltip { 20 | background: LightYellow none repeat scroll 0 0; 21 | border: 1px solid #CBE087; 22 | color: #000000; 23 | } 24 | .sourceRow[exeline="true"] { 25 | background-color: lightgoldenrodyellow; 26 | outline-color: #D9D9B6; 27 | outline-style: solid; 28 | outline-width: 1px; 29 | } 30 | .xml-text { 31 | color: black; 32 | } 33 | .whitespace { 34 | color: black; 35 | } 36 | .xml-punctuation { 37 | color: gray; 38 | } 39 | .xml-tagname { 40 | color: blue; 41 | } 42 | .xml-attname { 43 | color: darkred; 44 | } 45 | .xml-attribute { 46 | color: darkgreen; 47 | } 48 | .css-at { 49 | color: darkred; 50 | } 51 | .css-string { 52 | color: red; 53 | } 54 | .css-punctuation { 55 | color: midnightblue; 56 | } 57 | .js-keyword { 58 | color: blue; 59 | } 60 | .js-variable { 61 | color: black; 62 | } 63 | .js-operator { 64 | color: black; 65 | } 66 | .js-punctuation { 67 | color: darkBlue; 68 | } 69 | .js-variabledef { 70 | color: darkslategray; 71 | } 72 | .js-localvariable { 73 | color: darkslateBlue; 74 | } 75 | .js-property { 76 | color: teal; 77 | } 78 | .js-string { 79 | color: darkgreen; 80 | } 81 | .js-atom { 82 | color: saddleBrown; 83 | } 84 | .xml-comment { 85 | color: gray; 86 | } 87 | .css-identifier { 88 | color: midnightBlue; 89 | } 90 | .css-select-op { 91 | color: cadetblue; 92 | } 93 | .css-unit { 94 | color: orangered; 95 | } 96 | .css-value { 97 | color: black; 98 | } 99 | .css-colorcode { 100 | color: magenta; 101 | } 102 | .js-comment { 103 | color: gray; 104 | } 105 | .js-regexp { 106 | color: magenta; 107 | } 108 | .xml-entity { 109 | color: darkgoldenrod; 110 | } 111 | .xml-error { 112 | color: orangered; 113 | } 114 | .css-comment { 115 | color: gray; 116 | } -------------------------------------------------------------------------------- /themes/slush_poppies.css: -------------------------------------------------------------------------------- 1 | /* Slush & Poppies by William D. Neumann, converted from TextMate theme (Slush & Poppies.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #F1F1F1; 6 | font-family: Monaco, Courier New; 7 | color: #000000; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #B0B0FF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #406040; 20 | } 21 | 22 | .js-string { 23 | color: #C03030; 24 | } 25 | 26 | .js-atom { 27 | color: #0080A0; 28 | } 29 | 30 | .js-keyword { 31 | color: #2060A0; 32 | } 33 | -------------------------------------------------------------------------------- /themes/spacecadet.css: -------------------------------------------------------------------------------- 1 | /* SpaceCadet by Alex Ross, converted from TextMate theme (SpaceCadet.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #0D0D0D; 6 | font-family: Monaco, Courier New; 7 | color: #DDE6CF; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #000000; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #40002F; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #473C45; 20 | } 21 | 22 | .js-string { 23 | color: #805978; 24 | } 25 | 26 | .js-atom { 27 | color: #A8885A; 28 | } 29 | -------------------------------------------------------------------------------- /themes/sunburst.css: -------------------------------------------------------------------------------- 1 | /* Sunburst by Stanley Rost, converted from TextMate theme (Sunburst.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #000000; 6 | font-family: Monaco, Courier New; 7 | color: #F8F8F8; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #FFFFFF; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #DDF0FF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #AEAEAE; 20 | } 21 | 22 | .js-atom { 23 | color: #3387CC; 24 | } 25 | 26 | .js-keyword { 27 | color: #E28964; 28 | } 29 | 30 | .js-string { 31 | color: #65B042; 32 | } 33 | 34 | .js-variable { 35 | color: #3E87E3; 36 | } 37 | 38 | .js-regexp { 39 | color: #E9C062; 40 | } 41 | 42 | .xml-tagname { 43 | color: #89BDFF; 44 | } 45 | 46 | .xml-attname { 47 | color: #E0C589; 48 | } 49 | 50 | .xml-attname { 51 | color: #E18964; 52 | } 53 | 54 | .css-identifier { 55 | color: #CDA869; 56 | } 57 | 58 | .xml-attname { 59 | color: #8F9D6A; 60 | } 61 | 62 | .xml-attname { 63 | color: #8B98AB; 64 | } 65 | 66 | .xml-attname { 67 | color: #9B703F; 68 | } 69 | 70 | .css-colorcode { 71 | color: #C5AF75; 72 | } 73 | 74 | .css-value { 75 | color: #F9EE98; 76 | } 77 | 78 | .css-at { 79 | color: #8693A5; 80 | } 81 | -------------------------------------------------------------------------------- /themes/twilight.css: -------------------------------------------------------------------------------- 1 | /* Twilight by Michael Sheets, converted from TextMate theme (Twilight.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #141414; 6 | font-family: Monaco, Courier New; 7 | color: #F8F8F8; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #FFFFFF; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #DDF0FF; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #5F5A60; 20 | } 21 | 22 | .js-atom { 23 | color: #CF6A4C; 24 | } 25 | 26 | .js-keyword { 27 | color: #CDA869; 28 | } 29 | 30 | .js-string { 31 | color: #8F9D6A; 32 | } 33 | 34 | .js-variable { 35 | color: #7587A6; 36 | } 37 | 38 | .js-regexp { 39 | color: #E9C062; 40 | } 41 | 42 | .xml-processing { 43 | color: #494949; 44 | } 45 | 46 | .xml-tagname { 47 | color: #AC885B; 48 | } 49 | 50 | .xml-attname { 51 | color: #E0C589; 52 | } 53 | 54 | .css-identifier { 55 | color: #CDA869; 56 | } 57 | 58 | .xml-attname { 59 | color: #8F9D6A; 60 | } 61 | 62 | .xml-attname { 63 | color: #8B98AB; 64 | } 65 | 66 | .xml-attname { 67 | color: #9B703F; 68 | } 69 | 70 | .css-colorcode { 71 | color: #C5AF75; 72 | } 73 | 74 | .css-value { 75 | color: #F9EE98; 76 | } 77 | 78 | .css-at { 79 | color: #8693A5; 80 | } 81 | -------------------------------------------------------------------------------- /themes/zenburnesque.css: -------------------------------------------------------------------------------- 1 | /* Zenburnesque by William D. Neumann, converted from TextMate theme (Zenburnesque.tmTheme) */ 2 | 3 | .panelNode-script { 4 | font-size: 11px; 5 | background-color: #404040; 6 | font-family: Monaco, Courier New; 7 | color: #DEDEDE; 8 | } 9 | 10 | .sourceRow.hovered { 11 | background-color: #A08040; 12 | } 13 | 14 | .sourceRow[exeLine="true"] { 15 | background-color: #A0A0C0; 16 | } 17 | 18 | .js-comment, .xml-comment, .css-comment { 19 | color: #709070; 20 | } 21 | 22 | .js-string { 23 | color: #FF2020; 24 | } 25 | 26 | .js-atom { 27 | color: #22C0FF; 28 | } 29 | 30 | .js-keyword { 31 | color: #FFFFA0; 32 | } 33 | -------------------------------------------------------------------------------- /utils/Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'Plist' 3 | rescue LoadError 4 | raise 'You must "gem install plist" to get plist parser' 5 | end 6 | 7 | ROOT = File.expand_path('..') 8 | SRC = ROOT 9 | DST = File.join(ROOT, 'build') 10 | DST_THEMES = File.join(DST, 'themes') 11 | TMP = File.join(ROOT, 'tmp') 12 | 13 | THEMES_DIR = File.expand_path("~/Library/Application\\ Support/TextMate/Themes") 14 | 15 | # http://kpumuk.info/ruby-on-rails/colorizing-console-ruby-script-output/ 16 | begin 17 | require 'Win32/Console/ANSI' if PLATFORM =~ /win32/ 18 | rescue LoadError 19 | raise 'You must "gem install win32console" to use terminal colors on Windows' 20 | end 21 | 22 | def colorize(text, color_code) 23 | "#{color_code}#{text}\e[0m" 24 | end 25 | 26 | def red(text); colorize(text, "\e[31m"); end 27 | def green(text); colorize(text, "\e[32m"); end 28 | def yellow(text); colorize(text, "\e[33m"); end 29 | def blue(text); colorize(text, "\e[34m"); end 30 | def magenta(text); colorize(text, "\e[35m"); end 31 | def azure(text); colorize(text, "\e[36m"); end 32 | def white(text); colorize(text, "\e[37m"); end 33 | def black(text); colorize(text, "\e[30m"); end 34 | 35 | def file_color(text); yellow(text); end 36 | def dir_color(text); blue(text); end 37 | def cmd_color(text); azure(text); end 38 | 39 | def detect_selector_from_scope(scope) 40 | return ".panelNode-script" unless scope 41 | parts = scope.split(',') 42 | parts.each do |part| 43 | case part 44 | when "comment" 45 | return ".js-comment, .xml-comment, .css-comment" 46 | when "keyword" 47 | return ".js-keyword" 48 | when "variable", "constant.language" 49 | return ".js-variable" 50 | when "variable.language", "variable.other" 51 | return ".js-variabledef" 52 | when "string", "css.string" 53 | return ".js-string" 54 | when "string.regexp" 55 | return ".js-regexp" 56 | when "constant", "constant.numeric" 57 | return ".js-atom" 58 | when "keyword.operator.js" 59 | return ".js-operator" 60 | when "string.quoted.docinfo.doctype.DTD", "meta.tag.preprocessor.xml", "meta.tag.sgml.doctype", "meta.tag.sgml.doctype entity", "meta.tag.sgml.doctype string", "meta.tag.preprocessor.xml", "meta.tag.preprocessor.xml entity", "meta.tag.preprocessor.xml string" 61 | return ".xml-processing" 62 | when "entity.name.tag", "meta.tag", "declaration.tag" 63 | return ".xml-tagname" 64 | when /attribute-name/ 65 | return ".xml-attname" 66 | when "???" 67 | return ".xml-text" 68 | when "???" 69 | return ".xml-entity" 70 | when "???" 71 | return ".xml-cdata" 72 | when /property-value.css/ 73 | return ".css-value" 74 | when "meta.selector.css entity.name.tag" 75 | return ".css-identifier" 76 | when /property-name.css/ 77 | return ".css-colorcode" 78 | when "???" 79 | return ".css-string" 80 | when "???" 81 | return ".css-unit" 82 | when "???" 83 | return ".css-important" 84 | when "???" 85 | return ".css-select-op" 86 | when "???" 87 | return ".css-punctuation" 88 | when "???" 89 | return ".css-compare" 90 | when /at-rule/ 91 | return ".css-at" 92 | end 93 | end 94 | 95 | nil 96 | end 97 | 98 | def color(val) 99 | return "???" unless val =~ /#([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])/ 100 | "\##{$1}" 101 | end 102 | 103 | def font_style(val) 104 | return nil unless val=="italic" 105 | "italic" 106 | end 107 | 108 | def font_weight(val) 109 | return nil unless val=="bold" 110 | "bold" 111 | end 112 | 113 | KNOWN_ATTRIBUTES = { 114 | "foreground" => ["color", method(:color)], 115 | "background" => ["background-color", method(:color)], 116 | "fontStyle" => ["font-style", method(:font_style)], 117 | "fontStyle" => ["font-weight", method(:font_weight)], 118 | } 119 | 120 | def gen_rule(dict) 121 | scope = dict["scope"] 122 | selector = detect_selector_from_scope(scope) 123 | return "" unless selector 124 | settings = dict["settings"] 125 | return "" unless settings 126 | 127 | rule = {} 128 | KNOWN_ATTRIBUTES.each do |attr, spec| 129 | next unless settings[attr] 130 | spec = [spec] unless spec.is_a?(Array) 131 | 132 | val = settings[attr] 133 | val = spec[1].call(val) if spec[1] 134 | rule[spec[0]] = val if val 135 | end 136 | 137 | return "" unless rule.keys.size>0 138 | 139 | if (selector==".panelNode-script") then 140 | rule["font-family"] = "Monaco, Courier New" 141 | rule["font-size"] = "11px" 142 | 143 | additional = "" 144 | highlight = color(settings["lineHighlight"]) if settings["lineHighlight"] 145 | additional += ".sourceRow.hovered { background-color: #{highlight}; }\n\n" if highlight 146 | selection = color(settings["selection"]) if settings["selection"] 147 | additional += ".sourceRow[exeLine=\"true\"] { background-color: #{selection}; }\n\n" if selection 148 | end 149 | 150 | res = "#{selector} {\n" 151 | rule.each do |key, value| 152 | res += " #{key}: #{value};\n" 153 | end 154 | res += "}\n\n" 155 | 156 | res += additional if additional 157 | res 158 | end 159 | 160 | def gen_css(data, source) 161 | desc = "#{data["name"]}" 162 | desc += " by #{data["author"]}" if data["author"] 163 | desc += ", converted from TextMate theme (#{source})" 164 | 165 | res = "/* #{desc} */\n\n" 166 | 167 | data["settings"].each do |dict| 168 | res += gen_rule(dict) 169 | end 170 | 171 | { 172 | "css" => res, 173 | "name" => data["name"], 174 | "author" => data["author"], 175 | "description" => desc 176 | } 177 | end 178 | 179 | def my_mkdir(dir) 180 | puts "#{cmd_color('creating directory')} #{dir_color(dir)}" 181 | mkdir dir 182 | end 183 | 184 | def process(dir) 185 | files = Dir.glob(File.join(dir, "**", "*.tmTheme")) 186 | files.each do |filename| 187 | basename = File.basename(filename) 188 | puts " Converting #{file_color(basename)}" 189 | data = Plist::parse_xml(filename) 190 | preset = gen_css(data, basename) 191 | preset["basename"] = basename 192 | yield preset 193 | end 194 | end 195 | 196 | task :convert do 197 | my_mkdir(DST_THEMES) unless File.exist?(DST_THEMES) 198 | puts "Scanning #{dir_color(THEMES_DIR)} ..." 199 | process(THEMES_DIR) do |preset| 200 | res = File.join(DST_THEMES, preset["basename"] + ".css") 201 | File.open(res, "w") do |file| 202 | file.write preset["css"] 203 | end 204 | end 205 | end 206 | 207 | task :sql do 208 | puts "Generating sql ..." 209 | 210 | res = "" 211 | process(THEMES_DIR) do |preset| 212 | name = preset["name"].downcase.gsub(/[\(\)&]/, "").gsub(/[ \t]/, "_").gsub(/'/, "''") 213 | title = preset["name"].gsub(/'/, "''") 214 | desc = preset["description"].gsub(/'/, "''") 215 | code = preset["css"].gsub(/\n/, "\\r\\n").gsub(/'/, "''").gsub(/"/, """) 216 | res += "('#{name}', '#{title}', '#{desc}', '#{code}', '2008-06-14 18:19:36', '2008-06-14 18:24:04'),\n" 217 | end 218 | 219 | puts res 220 | end 221 | 222 | task :default => :convert --------------------------------------------------------------------------------