diff options
| author | Marak <[email protected]> | 2016-03-03 04:38:00 -0500 |
|---|---|---|
| committer | Marak <[email protected]> | 2016-03-03 04:38:00 -0500 |
| commit | aff89dcfbf8611473a1dabb6a6cb1ad17ae34d47 (patch) | |
| tree | 301a4d899c0c49af0e4e1b859342a740c5158bf2 /doc/scripts/sunlight.js | |
| parent | c252feb52e896f153fd969377532d77777903d69 (diff) | |
| download | faker-aff89dcfbf8611473a1dabb6a6cb1ad17ae34d47.tar.xz faker-aff89dcfbf8611473a1dabb6a6cb1ad17ae34d47.zip | |
[dist] Check /docs back into git #350
Diffstat (limited to 'doc/scripts/sunlight.js')
| -rw-r--r-- | doc/scripts/sunlight.js | 1157 |
1 files changed, 1157 insertions, 0 deletions
diff --git a/doc/scripts/sunlight.js b/doc/scripts/sunlight.js new file mode 100644 index 00000000..b97028cf --- /dev/null +++ b/doc/scripts/sunlight.js @@ -0,0 +1,1157 @@ +/** + * Sunlight + * Intelligent syntax highlighting + * + * http://sunlightjs.com/ + * + * by Tommy Montgomery <http://tmont.com> + * Licensed under WTFPL <http://sam.zoy.org/wtfpl/> + */ +(function(window, document, undefined){ + + var + //http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html + //we have to sniff this because IE requires \r + isIe = !+"\v1", + EOL = isIe ? "\r" : "\n", + EMPTY = function() { return null; }, + HIGHLIGHTED_NODE_COUNT = 0, + DEFAULT_LANGUAGE = "plaintext", + DEFAULT_CLASS_PREFIX = "sunlight-", + + //global sunlight variables + defaultAnalyzer, + getComputedStyle, + globalOptions = { + tabWidth: 4, + classPrefix: DEFAULT_CLASS_PREFIX, + showWhitespace: false, + maxHeight: false + }, + languages = {}, + languageDefaults = {}, + events = { + beforeHighlightNode: [], + beforeHighlight: [], + beforeTokenize: [], + afterTokenize: [], + beforeAnalyze: [], + afterAnalyze: [], + afterHighlight: [], + afterHighlightNode: [] + }; + + defaultAnalyzer = (function() { + function defaultHandleToken(suffix) { + return function(context) { + var element = document.createElement("span"); + element.className = context.options.classPrefix + suffix; + element.appendChild(context.createTextNode(context.tokens[context.index])); + return context.addNode(element) || true; + }; + } + + return { + handleToken: function(context) { + return defaultHandleToken(context.tokens[context.index].name)(context); + }, + + //just append default content as a text node + handle_default: function(context) { + return context.addNode(context.createTextNode(context.tokens[context.index])); + }, + + //this handles the named ident mayhem + handle_ident: function(context) { + var evaluate = function(rules, createRule) { + var i; + rules = rules || []; + for (i = 0; i < rules.length; i++) { + if (typeof(rules[i]) === "function") { + if (rules[i](context)) { + return defaultHandleToken("named-ident")(context); + } + } else if (createRule && createRule(rules[i])(context.tokens)) { + return defaultHandleToken("named-ident")(context); + } + } + + return false; + }; + + return evaluate(context.language.namedIdentRules.custom) + || evaluate(context.language.namedIdentRules.follows, function(ruleData) { return createProceduralRule(context.index - 1, -1, ruleData, context.language.caseInsensitive); }) + || evaluate(context.language.namedIdentRules.precedes, function(ruleData) { return createProceduralRule(context.index + 1, 1, ruleData, context.language.caseInsensitive); }) + || evaluate(context.language.namedIdentRules.between, function(ruleData) { return createBetweenRule(context.index, ruleData.opener, ruleData.closer, context.language.caseInsensitive); }) + || defaultHandleToken("ident")(context); + } + }; + }()); + + languageDefaults = { + analyzer: create(defaultAnalyzer), + customTokens: [], + namedIdentRules: {}, + punctuation: /[^\w\s]/, + numberParser: defaultNumberParser, + caseInsensitive: false, + doNotParse: /\s/, + contextItems: {}, + embeddedLanguages: {} + }; + + //adapted from http://blargh.tommymontgomery.com/2010/04/get-computed-style-in-javascript/ + getComputedStyle = (function() { + var func = null; + if (document.defaultView && document.defaultView.getComputedStyle) { + func = document.defaultView.getComputedStyle; + } else { + func = function(element, anything) { + return element["currentStyle"] || {}; + }; + } + + return function(element, style) { + return func(element, null)[style]; + } + }()); + + //----------- + //FUNCTIONS + //----------- + + function createCodeReader(text) { + var index = 0, + line = 1, + column = 1, + length, + EOF = undefined, + currentChar, + nextReadBeginsLine; + + text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); //normalize line endings to unix + + length = text.length; + currentChar = length > 0 ? text.charAt(0) : EOF; + + function getCharacters(count) { + var value; + if (count === 0) { + return ""; + } + + count = count || 1; + + value = text.substring(index + 1, index + count + 1); + return value === "" ? EOF : value; + } + + return { + toString: function() { + return "length: " + length + ", index: " + index + ", line: " + line + ", column: " + column + ", current: [" + currentChar + "]"; + }, + + peek: function(count) { + return getCharacters(count); + }, + + substring: function() { + return text.substring(index); + }, + + peekSubstring: function() { + return text.substring(index + 1); + }, + + read: function(count) { + var value = getCharacters(count), + newlineCount, + lastChar; + + if (value === "") { + //this is a result of reading/peeking/doing nothing + return value; + } + + if (value !== EOF) { + //advance index + index += value.length; + column += value.length; + + //update line count + if (nextReadBeginsLine) { + line++; + column = 1; + nextReadBeginsLine = false; + } + + newlineCount = value.substring(0, value.length - 1).replace(/[^\n]/g, "").length; + if (newlineCount > 0) { + line += newlineCount; + column = 1; + } + + lastChar = last(value); + if (lastChar === "\n") { + nextReadBeginsLine = true; + } + + currentChar = lastChar; + } else { + index = length; + currentChar = EOF; + } + + return value; + }, + + text: function() { return text; }, + + getLine: function() { return line; }, + getColumn: function() { return column; }, + isEof: function() { return index >= length; }, + isSol: function() { return column === 1; }, + isSolWs: function() { + var temp = index, + c; + if (column === 1) { + return true; + } + + //look backward until we find a newline or a non-whitespace character + while ((c = text.charAt(--temp)) !== "") { + if (c === "\n") { + return true; + } + if (!/\s/.test(c)) { + return false; + } + } + + return true; + }, + isEol: function() { return nextReadBeginsLine; }, + EOF: EOF, + current: function() { return currentChar; } + }; + } + + //http://javascript.crockford.com/prototypal.html + function create(o) { + function F() {} + F.prototype = o; + return new F(); + } + + function appendAll(parent, children) { + var i; + for (i = 0; i < children.length; i++) { + parent.appendChild(children[i]); + } + } + + //gets the last character in a string or the last element in an array + function last(thing) { + return thing.charAt ? thing.charAt(thing.length - 1) : thing[thing.length - 1]; + } + + //array.contains() + function contains(arr, value, caseInsensitive) { + var i; + if (arr.indexOf && !caseInsensitive) { + return arr.indexOf(value) >= 0; + } + + for (i = 0; i < arr.length; i++) { + if (arr[i] === value) { + return true; + } + + if (caseInsensitive && typeof(arr[i]) === "string" && typeof(value) === "string" && arr[i].toUpperCase() === value.toUpperCase()) { + return true; + } + } + + return false; + } + + //non-recursively merges one object into the other + function merge(defaultObject, objectToMerge) { + var key; + if (!objectToMerge) { + return defaultObject; + } + + for (key in objectToMerge) { + defaultObject[key] = objectToMerge[key]; + } + + return defaultObject; + } + + function clone(object) { + return merge({}, object); + } + + //http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript/3561711#3561711 + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); + } + + function createProceduralRule(startIndex, direction, tokenRequirements, caseInsensitive) { + tokenRequirements = tokenRequirements.slice(0); + return function(tokens) { + var tokenIndexStart = startIndex, + j, + expected, + actual; + + if (direction === 1) { + tokenRequirements.reverse(); + } + + for (j = 0; j < tokenRequirements.length; j++) { + actual = tokens[tokenIndexStart + (j * direction)]; + expected = tokenRequirements[tokenRequirements.length - 1 - j]; + + if (actual === undefined) { + if (expected["optional"] !== undefined && expected.optional) { + tokenIndexStart -= direction; + } else { + return false; + } + } else if (actual.name === expected.token && (expected["values"] === undefined || contains(expected.values, actual.value, caseInsensitive))) { + //derp + continue; + } else if (expected["optional"] !== undefined && expected.optional) { + tokenIndexStart -= direction; //we need to reevaluate against this token again + } else { + return false; + } + } + + return true; + }; + } + + function createBetweenRule(startIndex, opener, closer, caseInsensitive) { + return function(tokens) { + var index = startIndex, + token, + success = false; + + //check to the left: if we run into a closer or never run into an opener, fail + while ((token = tokens[--index]) !== undefined) { + if (token.name === closer.token && contains(closer.values, token.value)) { + if (token.name === opener.token && contains(opener.values, token.value, caseInsensitive)) { + //if the closer is the same as the opener that's okay + success = true; + break; + } + + return false; + } + + if (token.name === opener.token && contains(opener.values, token.value, caseInsensitive)) { + success = true; + break; + } + } + + if (!success) { + return false; + } + + //check to the right for the closer + index = startIndex; + while ((token = tokens[++index]) !== undefined) { + if (token.name === opener.token && contains(opener.values, token.value, caseInsensitive)) { + if (token.name === closer.token && contains(closer.values, token.value, caseInsensitive)) { + //if the closer is the same as the opener that's okay + success = true; + break; + } + + return false; + } + + if (token.name === closer.token && contains(closer.values, token.value, caseInsensitive)) { + success = true; + break; + } + } + + return success; + }; + } + + function matchWord(context, wordMap, tokenName, doNotRead) { + var current = context.reader.current(), + i, + word, + peek, + line = context.reader.getLine(), + column = context.reader.getColumn(); + + wordMap = wordMap || []; + if (context.language.caseInsensitive) { + current = current.toUpperCase(); + } + + if (!wordMap[current]) { + return null; + } + + wordMap = wordMap[current]; + for (i = 0; i < wordMap.length; i++) { + word = wordMap[i].value; + + peek = current + context.reader.peek(word.length); + if (word === peek || wordMap[i].regex.test(peek)) { + return context.createToken( + tokenName, + context.reader.current() + context.reader[doNotRead ? "peek" : "read"](word.length - 1), + line, + column + ); + } + } + + return null; + } + + //gets the next token in the specified direction while matcher matches the current token + function getNextWhile(tokens, index, direction, matcher) { + var count = 1, + token; + + direction = direction || 1; + while (token = tokens[index + (direction * count++)]) { + if (!matcher(token)) { + return token; + } + } + + return undefined; + } + + //this is crucial for performance + function createHashMap(wordMap, boundary, caseInsensitive) { + //creates a hash table where the hash is the first character of the word + var newMap = { }, + i, + word, + firstChar; + + for (i = 0; i < wordMap.length; i++) { + word = caseInsensitive ? wordMap[i].toUpperCase() : wordMap[i]; + firstChar = word.charAt(0); + if (!newMap[firstChar]) { + newMap[firstChar] = []; + } + + newMap[firstChar].push({ value: word, regex: new RegExp("^" + regexEscape(word) + boundary, caseInsensitive ? "i" : "") }); + } + + return newMap; + } + + function defaultNumberParser(context) { + var current = context.reader.current(), + number, + line = context.reader.getLine(), + column = context.reader.getColumn(), + allowDecimal = true, + peek; + + if (!/\d/.test(current)) { + //is it a decimal followed by a number? + if (current !== "." || !/\d/.test(context.reader.peek())) { + return null; + } + + //decimal without leading zero + number = current + context.reader.read(); + allowDecimal = false; + } else { + number = current; + if (current === "0" && context.reader.peek() !== ".") { + //hex or octal + allowDecimal = false; + } + } + + //easy way out: read until it's not a number or letter + //this will work for hex (0xef), octal (012), decimal and scientific notation (1e3) + //anything else and you're on your own + + while ((peek = context.reader.peek()) !== context.reader.EOF) { + if (!/[A-Za-z0-9]/.test(peek)) { + if (peek === "." && allowDecimal && /\d$/.test(context.reader.peek(2))) { + number += context.reader.read(); + allowDecimal = false; + continue; + } + + break; + } + + number += context.reader.read(); + } + + return context.createToken("number", number, line, column); + } + + function fireEvent(eventName, highlighter, eventContext) { + var delegates = events[eventName] || [], + i; + + for (i = 0; i < delegates.length; i++) { + delegates[i].call(highlighter, eventContext); + } + } + + function Highlighter(options) { + this.options = merge(clone(globalOptions), options); + } + + Highlighter.prototype = (function() { + var parseNextToken = (function() { + function isIdentMatch(context) { + return context.language.identFirstLetter && context.language.identFirstLetter.test(context.reader.current()); + } + + //token parsing functions + function parseKeyword(context) { + return matchWord(context, context.language.keywords, "keyword"); + } + + function parseCustomTokens(context) { + var tokenName, + token; + if (context.language.customTokens === undefined) { + return null; + } + + for (tokenName in context.language.customTokens) { + token = matchWord(context, context.language.customTokens[tokenName], tokenName); + if (token !== null) { + return token; + } + } + + return null; + } + + function parseOperator(context) { + return matchWord(context, context.language.operators, "operator"); + } + + function parsePunctuation(context) { + var current = context.reader.current(); + if (context.language.punctuation.test(regexEscape(current))) { + return context.createToken("punctuation", current, context.reader.getLine(), context.reader.getColumn()); + } + + return null; + } + + function parseIdent(context) { + var ident, + peek, + line = context.reader.getLine(), + column = context.reader.getColumn(); + + if (!isIdentMatch(context)) { + return null; + } + + ident = context.reader.current(); + while ((peek = context.reader.peek()) !== context.reader.EOF) { + if (!context.language.identAfterFirstLetter.test(peek)) { + break; + } + + ident += context.reader.read(); + } + + return context.createToken("ident", ident, line, column); + } + + function parseDefault(context) { + if (context.defaultData.text === "") { + //new default token + context.defaultData.line = context.reader.getLine(); + context.defaultData.column = context.reader.getColumn(); + } + + context.defaultData.text += context.reader.current(); + return null; + } + + function parseScopes(context) { + var current = context.reader.current(), + tokenName, + specificScopes, + j, + opener, + line, + column, + continuation, + value; + + for (tokenName in context.language.scopes) { + specificScopes = context.language.scopes[tokenName]; + for (j = 0; j < specificScopes.length; j++) { + opener = specificScopes[j][0]; + + value = current + context.reader.peek(opener.length - 1); + + if (opener !== value && (!context.language.caseInsensitive || value.toUpperCase() !== opener.toUpperCase())) { + continue; + } + + line = context.reader.getLine(), column = context.reader.getColumn(); + context.reader.read(opener.length - 1); + continuation = getScopeReaderFunction(specificScopes[j], tokenName); + return continuation(context, continuation, value, line, column); + } + } + + return null; + } + + function parseNumber(context) { + return context.language.numberParser(context); + } + + function parseCustomRules(context) { + var customRules = context.language.customParseRules, + i, + token; + + if (customRules === undefined) { + return null; + } + + for (i = 0; i < customRules.length; i++) { + token = customRules[i](context); + if (token) { + return token; + } + } + + return null; + } + + return function(context) { + if (context.language.doNotParse.test(context.reader.current())) { + return parseDefault(context); + } + + return parseCustomRules(context) + || parseCustomTokens(context) + || parseKeyword(context) + || parseScopes(context) + || parseIdent(context) + || parseNumber(context) + || parseOperator(context) + || parsePunctuation(context) + || parseDefault(context); + } + }()); + + function getScopeReaderFunction(scope, tokenName) { + var escapeSequences = scope[2] || [], + closerLength = scope[1].length, + closer = typeof(scope[1]) === "string" ? new RegExp(regexEscape(scope[1])) : scope[1].regex, + zeroWidth = scope[3] || false; + + //processCurrent indicates that this is being called from a continuation + //which means that we need to process the current char, rather than peeking at the next + return function(context, continuation, buffer, line, column, processCurrent) { + var foundCloser = false; + buffer = buffer || ""; + + processCurrent = processCurrent ? 1 : 0; + + function process(processCurrent) { + //check for escape sequences + var peekValue, + current = context.reader.current(), + i; + + for (i = 0; i < escapeSequences.length; i++) { + peekValue = (processCurrent ? current : "") + context.reader.peek(escapeSequences[i].length - processCurrent); + if (peekValue === escapeSequences[i]) { + buffer += context.reader.read(peekValue.length - processCurrent); + return true; + } + } + + peekValue = (processCurrent ? current : "") + context.reader.peek(closerLength - processCurrent); + if (closer.test(peekValue)) { + foundCloser = true; + return false; + } + + buffer += processCurrent ? current : context.reader.read(); + return true; + }; + + if (!processCurrent || process(true)) { + while (context.reader.peek() !== context.reader.EOF && process(false)) { } + } + + if (processCurrent) { + buffer += context.reader.current(); + context.reader.read(); + } else { + buffer += zeroWidth || context.reader.peek() === context.reader.EOF ? "" : context.reader.read(closerLength); + } + + if (!foundCloser) { + //we need to signal to the context that this scope was never properly closed + //this has significance for partial parses (e.g. for nested languages) + context.continuation = continuation; + } + + return context.createToken(tokenName, buffer, line, column); + }; + } + + //called before processing the current + function switchToEmbeddedLanguageIfNecessary(context) { + var i, + embeddedLanguage; + + for (i = 0; i < context.language.embeddedLanguages.length; i++) { + if (!languages[context.language.embeddedLanguages[i].language]) { + //unregistered language + continue; + } + + embeddedLanguage = clone(context.language.embeddedLanguages[i]); + + if (embeddedLanguage.switchTo(context)) { + embeddedLanguage.oldItems = clone(context.items); + context.embeddedLanguageStack.push(embeddedLanguage); + context.language = languages[embeddedLanguage.language]; + context.items = merge(context.items, clone(context.language.contextItems)); + break; + } + } + } + + //called after processing the current + function switchBackFromEmbeddedLanguageIfNecessary(context) { + var current = last(context.embeddedLanguageStack), + lang; + + if (current && current.switchBack(context)) { + context.language = languages[current.parentLanguage]; + lang = context.embeddedLanguageStack.pop(); + + //restore old items + context.items = clone(lang.oldItems); + lang.oldItems = {}; + } + } + + function tokenize(unhighlightedCode, language, partialContext, options) { + var tokens = [], + context, + continuation, + token; + + fireEvent("beforeTokenize", this, { code: unhighlightedCode, language: language }); + context = { + reader: createCodeReader(unhighlightedCode), + language: language, + items: clone(language.contextItems), + token: function(index) { return tokens[index]; }, + getAllTokens: function() { return tokens.slice(0); }, + count: function() { return tokens.length; }, + options: options, + embeddedLanguageStack: [], + + defaultData: { + text: "", + line: 1, + column: 1 + }, + createToken: function(name, value, line, column) { + return { + name: name, + line: line, + value: isIe ? value.replace(/\n/g, "\r") : value, + column: column, + language: this.language.name + }; + } + }; + + //if continuation is given, then we need to pick up where we left off from a previous parse + //basically it indicates that a scope was never closed, so we need to continue that scope + if (partialContext.continuation) { + continuation = partialContext.continuation; + partialContext.continuation = null; + tokens.push(continuation(context, continuation, "", context.reader.getLine(), context.reader.getColumn(), true)); + } + + while (!context.reader.isEof()) { + switchToEmbeddedLanguageIfNecessary(context); + token = parseNextToken(context); + + //flush default data if needed (in pretty much all languages this is just whitespace) + if (token !== null) { + if (context.defaultData.text !== "") { + tokens.push(context.createToken("default", context.defaultData.text, context.defaultData.line, context.defaultData.column)); + context.defaultData.text = ""; + } + + if (token[0] !== undefined) { + //multiple tokens + tokens = tokens.concat(token); + } else { + //single token + tokens.push(token); + } + } + + switchBackFromEmbeddedLanguageIfNecessary(context); + context.reader.read(); + } + + //append the last default token, if necessary + if (context.defaultData.text !== "") { + tokens.push(context.createToken("default", context.defaultData.text, context.defaultData.line, context.defaultData.column)); + } + + fireEvent("afterTokenize", this, { code: unhighlightedCode, parserContext: context }); + return context; + } + + function createAnalyzerContext(parserContext, partialContext, options) { + var nodes = [], + prepareText = function() { + var nbsp, tab; + if (options.showWhitespace) { + nbsp = String.fromCharCode(0xB7); + tab = new Array(options.tabWidth).join(String.fromCharCode(0x2014)) + String.fromCharCode(0x2192); + } else { + nbsp = String.fromCharCode(0xA0); + tab = new Array(options.tabWidth + 1).join(nbsp); + } + + return function(token) { + var value = token.value.split(" ").join(nbsp), + tabIndex, + lastNewlineColumn, + actualColumn, + tabLength; + + //tabstop madness: replace \t with the appropriate number of characters, depending on the tabWidth option and its relative position in the line + while ((tabIndex = value.indexOf("\t")) >= 0) { + lastNewlineColumn = value.lastIndexOf(EOL, tabIndex); + actualColumn = lastNewlineColumn === -1 ? tabIndex : tabIndex - lastNewlineColumn - 1; + tabLength = options.tabWidth - (actualColumn % options.tabWidth); //actual length of the TAB character + + value = value.substring(0, tabIndex) + tab.substring(options.tabWidth - tabLength) + value.substring(tabIndex + 1); + } + + return value; + }; + }(); + + return { + tokens: (partialContext.tokens || []).concat(parserContext.getAllTokens()), + index: partialContext.index ? partialContext.index + 1 : 0, + language: null, + getAnalyzer: EMPTY, + options: options, + continuation: parserContext.continuation, + addNode: function(node) { nodes.push(node); }, + createTextNode: function(token) { return document.createTextNode(prepareText(token)); }, + getNodes: function() { return nodes; }, + resetNodes: function() { nodes = []; }, + items: parserContext.items + }; + } + + //partialContext allows us to perform a partial parse, and then pick up where we left off at a later time + //this functionality enables nested highlights (language within a language, e.g. PHP within HTML followed by more PHP) + function highlightText(unhighlightedCode, languageId, partialContext) { + var language = languages[languageId], + analyzerContext; + + partialContext = partialContext || { }; + if (language === undefined) { + //use default language if one wasn't specified or hasn't been registered + language = languages[DEFAULT_LANGUAGE]; + } + + fireEvent("beforeHighlight", this, { code: unhighlightedCode, language: language, previousContext: partialContext }); + + analyzerContext = createAnalyzerContext( + tokenize.call(this, unhighlightedCode, language, partialContext, this.options), + partialContext, + this.options + ); + + analyze.call(this, analyzerContext, partialContext.index ? partialContext.index + 1 : 0); + + fireEvent("afterHighlight", this, { analyzerContext: analyzerContext }); + + return analyzerContext; + } + + function createContainer(ctx) { + var container = document.createElement("span"); + container.className = ctx.options.classPrefix + ctx.language.name; + return container; + } + + function analyze(analyzerContext, startIndex) { + var nodes, + lastIndex, + container, + i, + tokenName, + func, + language, + analyzer; + + fireEvent("beforeAnalyze", this, { analyzerContext: analyzerContext }); + + if (analyzerContext.tokens.length > 0) { + analyzerContext.language = languages[analyzerContext.tokens[0].language] || languages[DEFAULT_LANGUAGE];; + nodes = []; + lastIndex = 0; + container = createContainer(analyzerContext); + + for (i = startIndex; i < analyzerContext.tokens.length; i++) { + language = languages[analyzerContext.tokens[i].language] || languages[DEFAULT_LANGUAGE]; + if (language.name !== analyzerContext.language.name) { + appendAll(container, analyzerContext.getNodes()); + analyzerContext.resetNodes(); + + nodes.push(container); + analyzerContext.language = language; + container = createContainer(analyzerContext); + } + + analyzerContext.index = i; + tokenName = analyzerContext.tokens[i].name; + func = "handle_" + tokenName; + + analyzer = analyzerContext.getAnalyzer.call(analyzerContext) || analyzerContext.language.analyzer; + analyzer[func] ? analyzer[func](analyzerContext) : analyzer.handleToken(analyzerContext); + } + + //append the last nodes, and add the final nodes to the context + appendAll(container, analyzerContext.getNodes()); + nodes.push(container); + analyzerContext.resetNodes(); + for (i = 0; i < nodes.length; i++) { + analyzerContext.addNode(nodes[i]); + } + } + + fireEvent("afterAnalyze", this, { analyzerContext: analyzerContext }); + } + + return { + //matches the language of the node to highlight + matchSunlightNode: function() { + var regex; + + return function(node) { + if (!regex) { + regex = new RegExp("(?:\\s|^)" + this.options.classPrefix + "highlight-(\\S+)(?:\\s|$)"); + } + + return regex.exec(node.className); + }; + }(), + + //determines if the node has already been highlighted + isAlreadyHighlighted: function() { + var regex; + return function(node) { + if (!regex) { + regex = new RegExp("(?:\\s|^)" + this.options.classPrefix + "highlighted(?:\\s|$)"); + } + + return regex.test(node.className); + }; + }(), + + //highlights a block of text + highlight: function(code, languageId) { return highlightText.call(this, code, languageId); }, + + //recursively highlights a DOM node + highlightNode: function highlightRecursive(node) { + var match, + languageId, + currentNodeCount, + j, + nodes, + k, + partialContext, + container, + codeContainer; + + if (this.isAlreadyHighlighted(node) || (match = this.matchSunlightNode(node)) === null) { + return; + } + + languageId = match[1]; + currentNodeCount = 0; + fireEvent("beforeHighlightNode", this, { node: node }); + for (j = 0; j < node.childNodes.length; j++) { + if (node.childNodes[j].nodeType === 3) { + //text nodes + partialContext = highlightText.call(this, node.childNodes[j].nodeValue, languageId, partialContext); + HIGHLIGHTED_NODE_COUNT++; + currentNodeCount = currentNodeCount || HIGHLIGHTED_NODE_COUNT; + nodes = partialContext.getNodes(); + + node.replaceChild(nodes[0], node.childNodes[j]); + for (k = 1; k < nodes.length; k++) { + node.insertBefore(nodes[k], nodes[k - 1].nextSibling); + } + } else if (node.childNodes[j].nodeType === 1) { + //element nodes + highlightRecursive.call(this, node.childNodes[j]); + } + } + + //indicate that this node has been highlighted + node.className += " " + this.options.classPrefix + "highlighted"; + + //if the node is block level, we put it in a container, otherwise we just leave it alone + if (getComputedStyle(node, "display") === "block") { + container = document.createElement("div"); + container.className = this.options.classPrefix + "container"; + + codeContainer = document.createElement("div"); + codeContainer.className = this.options.classPrefix + "code-container"; + + //apply max height if specified in options + if (this.options.maxHeight !== false) { + codeContainer.style.overflowY = "auto"; + codeContainer.style.maxHeight = this.options.maxHeight + (/^\d+$/.test(this.options.maxHeight) ? "px" : ""); + } + + container.appendChild(codeContainer); + + node.parentNode.insertBefore(codeContainer, node); + node.parentNode.removeChild(node); + codeContainer.appendChild(node); + + codeContainer.parentNode.insertBefore(container, codeContainer); + codeContainer.parentNode.removeChild(codeContainer); + container.appendChild(codeContainer); + } + + fireEvent("afterHighlightNode", this, { + container: container, + codeContainer: codeContainer, + node: node, + count: currentNodeCount + }); + } + }; + }()); + + //public facing object + window.Sunlight = { + version: "1.18", + Highlighter: Highlighter, + createAnalyzer: function() { return create(defaultAnalyzer); }, + globalOptions: globalOptions, + + highlightAll: function(options) { + var highlighter = new Highlighter(options), + tags = document.getElementsByTagName("*"), + i; + + for (i = 0; i < tags.length; i++) { + highlighter.highlightNode(tags[i]); + } + }, + + registerLanguage: function(languageId, languageData) { + var tokenName, + embeddedLanguages, + languageName; + + if (!languageId) { + throw "Languages must be registered with an identifier, e.g. \"php\" for PHP"; + } + + languageData = merge(merge({}, languageDefaults), languageData); + languageData.name = languageId; + + //transform keywords, operators and custom tokens into a hash map + languageData.keywords = createHashMap(languageData.keywords || [], "\\b", languageData.caseInsensitive); + languageData.operators = createHashMap(languageData.operators || [], "", languageData.caseInsensitive); + for (tokenName in languageData.customTokens) { + languageData.customTokens[tokenName] = createHashMap( + languageData.customTokens[tokenName].values, + languageData.customTokens[tokenName].boundary, + languageData.caseInsensitive + ); + } + + //convert the embedded language object to an easier-to-use array + embeddedLanguages = []; + for (languageName in languageData.embeddedLanguages) { + embeddedLanguages.push({ + parentLanguage: languageData.name, + language: languageName, + switchTo: languageData.embeddedLanguages[languageName].switchTo, + switchBack: languageData.embeddedLanguages[languageName].switchBack + }); + } + + languageData.embeddedLanguages = embeddedLanguages; + + languages[languageData.name] = languageData; + }, + + isRegistered: function(languageId) { return languages[languageId] !== undefined; }, + + bind: function(event, callback) { + if (!events[event]) { + throw "Unknown event \"" + event + "\""; + } + + events[event].push(callback); + }, + + util: { + last: last, + regexEscape: regexEscape, + eol: EOL, + clone: clone, + escapeSequences: ["\\n", "\\t", "\\r", "\\\\", "\\v", "\\f"], + contains: contains, + matchWord: matchWord, + createHashMap: createHashMap, + createBetweenRule: createBetweenRule, + createProceduralRule: createProceduralRule, + getNextNonWsToken: function(tokens, index) { return getNextWhile(tokens, index, 1, function(token) { return token.name === "default"; }); }, + getPreviousNonWsToken: function(tokens, index) { return getNextWhile(tokens, index, -1, function(token) { return token.name === "default"; }); }, + getNextWhile: function(tokens, index, matcher) { return getNextWhile(tokens, index, 1, matcher); }, + getPreviousWhile: function(tokens, index, matcher) { return getNextWhile(tokens, index, -1, matcher); }, + whitespace: { token: "default", optional: true }, + getComputedStyle: getComputedStyle + } + }; + + //register the default language + window.Sunlight.registerLanguage(DEFAULT_LANGUAGE, { punctuation: /(?!x)x/, numberParser: EMPTY }); + +}(this, document));
\ No newline at end of file |
