diff options
author | Alexander Sulfrian <alexander@sulfrian.net> | 2010-06-08 09:01:43 +0200 |
---|---|---|
committer | Alexander Sulfrian <alexander@sulfrian.net> | 2010-06-08 09:01:43 +0200 |
commit | d1fa08fdc9cb11dccee76d668ff85df30458c295 (patch) | |
tree | 1d19df6405103577d872902486792e8c23bce711 /infrastructure/ace/www/lexer_support.js | |
parent | d7c5ad7d6263fd1baf9bfdbaa4c50b70ef2fbdb2 (diff) | |
parent | 70d1f9d6fcaefe611e778b8dbf3bafea8934aa08 (diff) | |
download | etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.tar.gz etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.tar.xz etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.zip |
Merge remote branch 'upstream/master'
Conflicts:
etherpad/src/etherpad/control/pro/admin/pro_admin_control.js
etherpad/src/etherpad/control/pro/pro_main_control.js
etherpad/src/etherpad/control/pro_help_control.js
etherpad/src/etherpad/globals.js
etherpad/src/etherpad/legacy_urls.js
etherpad/src/etherpad/pne/pne_utils.js
etherpad/src/etherpad/pro/pro_utils.js
etherpad/src/main.js
etherpad/src/plugins/fileUpload/templates/fileUpload.ejs
etherpad/src/plugins/testplugin/templates/page.ejs
etherpad/src/static/css/pad2_ejs.css
etherpad/src/static/css/pro-help.css
etherpad/src/static/img/jun09/pad/protop.gif
etherpad/src/static/js/store.js
etherpad/src/themes/default/templates/framed/framedheader-pro.ejs
etherpad/src/themes/default/templates/main/home.ejs
etherpad/src/themes/default/templates/pro-help/main.ejs
etherpad/src/themes/default/templates/pro-help/pro-help-template.ejs
infrastructure/com.etherpad/licensing.scala
trunk/etherpad/src/etherpad/collab/ace/contentcollector.js
trunk/etherpad/src/etherpad/collab/ace/linestylefilter.js
trunk/etherpad/src/static/css/home-opensource.css
trunk/etherpad/src/static/js/ace.js
trunk/etherpad/src/static/js/linestylefilter_client.js
trunk/etherpad/src/templates/email/eepnet_license_info.ejs
trunk/etherpad/src/templates/pad/pad_body2.ejs
trunk/etherpad/src/templates/pad/pad_content.ejs
trunk/etherpad/src/templates/pad/padfull_body.ejs
trunk/etherpad/src/templates/pro/admin/pne-license-manager.ejs
Diffstat (limited to 'infrastructure/ace/www/lexer_support.js')
-rw-r--r-- | infrastructure/ace/www/lexer_support.js | 1068 |
1 files changed, 1068 insertions, 0 deletions
diff --git a/infrastructure/ace/www/lexer_support.js b/infrastructure/ace/www/lexer_support.js new file mode 100644 index 0000000..3d54f5c --- /dev/null +++ b/infrastructure/ace/www/lexer_support.js @@ -0,0 +1,1068 @@ +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/* This file is also a Helma module, referenced by its path! */ + +AceLexer = (function lexer_init() { + +// utility functions, make this file self-contained + +function forEach(array, func) { + for(var i=0;i<array.length;i++) { + var result = func(array[i], i); + if (result) break; + } +} + +function map(array, func) { + var result = []; + // must remain compatible with "arguments" pseudo-array + for(var i=0;i<array.length;i++) { + if (func) result.push(func(array[i], i)); + else result.push(array[i]); + } + return result; +} + +function filter(array, func) { + var result = []; + // must remain compatible with "arguments" pseudo-array + for(var i=0;i<array.length;i++) { + if (func(array[i], i)) result.push(array[i]); + } + return result; +} + +function isArray(testObject) { + return testObject && typeof testObject === 'object' && + !(testObject.propertyIsEnumerable('length')) && + typeof testObject.length === 'number'; +} + +// these three lines inspired by Steven Levithan's XRegExp +var singleLineRegex = /(?:[^[\\.]+|\\(?:[\S\s]|$)|\[\^?]?(?:[^\\\]]+|\\(?:[\S\s]|$))*]?)+|\./g; +var backReferenceRegex = /(?:[^\\[]+|\\(?:[^0-9]|$)|\[\^?]?(?:[^\\\]]+|\\(?:[\S\s]|$))*]?)+|\\([0-9]+)/g; +var parenFindingRegex = /(?:[^[(\\]+|\\(?:[\S\s]|$)|\[\^?]?(?:[^\\\]]+|\\(?:[\S\s]|$))*]?|\((?=\?))+|(\()/g; + +// Creates a function that, when called with (string, startIndex), finds the first of the patterns that +// matches the string starting at startIndex (and anchored there). Expects startIndex < string.length. +// The function returns a structure containing "whichCase", a number 0..(patterns.length-1) giving the +// index of the pattern that matched, or -1 if no pattern did, and "result", an array of the kind +// returned by RegExp.exec called on that pattern, or the array that would be returned by matching /[\S\s]/ +// (any single character) if no other pattern matched. Supports the flags 'i', 'm', and 's', where the +// effect of 's' is to make dots match all characters, including newlines. +// Patterns in general are not allowed to match zero-width strings, but a pattern that is specified +// as a regular expression literal with the 'm' flag is considered special, and may be zero-width, +// though as a consequence the match cannot include the final newline of the document. (Other flags +// on regular expression literals are ignored; use the "flags" argument instead.) +function makeRegexSwitch(patterns, flags) { + var numPatterns = patterns.length; + var patternStrings = map(patterns, function (p) { + if ((typeof p) == "string") + return p; // a string + else return p.source; // assume it's a regex + }); + var patternZeros = map(patterns, function (p) { + // using "multiline" is a special way to indicate the reg-ex is zero-width + return ((typeof p) != "string") && p.multiline; + }); + patternStrings.push("[\\S\\s]"); // default case + patternZeros.push(false); + // how many capturing groups each pattern has + var numGroups = map(patternStrings, function (p) { + var count = 0; + p.replace(parenFindingRegex, function (full,paren,offset) { if (paren) count++; }); + return count; + }); + // the group number for each case of the switch + var caseGroupNums = []; + var idx = 1; + forEach(numGroups, function (n) { caseGroupNums.push(idx); idx += n+1; }); + // make a big alternation of capturing groups + var alternation = map(patternStrings, function(p, pi) { + // correct the back-reference numbers + p = p.replace(backReferenceRegex, function (full, num) { + if (num) return "\\"+((+num)+caseGroupNums[pi]); + else return full; + }); + var extra = (patternZeros[pi] ? "[\\S\\s]": ""); // tack on another char for zero-widths + return '('+p+extra+')'; + }).join('|'); + // process regex flags + flags = (flags || ""); + var realFlags = "g"; + for(var i=0;i<flags.length;i++) { + var f = flags.charAt(i); + if (f == "i" || f == "m") realFlags += f; + else if (f == "s") { + alternation = alternation.replace(singleLineRegex, + function (x) { return x==='.' ? "[\\S\\s]" : x; }); + } + } + //console.log(alternation); + var bigRegex = new RegExp(alternation, realFlags); + return function (string, matchIndex) { + bigRegex.lastIndex = matchIndex; + var execResult = bigRegex.exec(string); + var whichCase; + var resultArray = []; + // search linearly for which case matched in the alternation + for(var i=0;i<=numPatterns;i++) { + var groupNum = caseGroupNums[i]; + if (execResult[groupNum]) { + whichCase = i; + for(var j=0;j<=numGroups[i];j++) { + var r = execResult[groupNum+j]; + if (patternZeros[i] && j==0) { + r = r.substring(0, r.length-1); + } + resultArray[j] = r; + } + break; + } + } + if (whichCase == numPatterns) + whichCase = -1; // default case + return {whichCase: whichCase, result: resultArray}; + } +} + + +var tokenClasses = { + 'Token': '', + + 'Text': '', + 'TEST': 'test', + 'Whitespace': 'w', + 'Error': 'err', + 'Other': 'x', + 'Dirty': 'dirty', + + 'Keyword': 'k', + 'Keyword.Constant': 'kc', + 'Keyword.Declaration': 'kd', + 'Keyword.Pseudo': 'kp', + 'Keyword.Reserved': 'kr', + 'Keyword.Type': 'kt', + + 'Name': 'n', + 'Name.Attribute': 'na', + 'Name.Builtin': 'nb', + 'Name.Builtin.Pseudo': 'bp', + 'Name.Class': 'nc', + 'Name.Constant': 'no', + 'Name.Decorator': 'nd', + 'Name.Entity': 'ni', + 'Name.Exception': 'ne', + 'Name.Function': 'nf', + 'Name.Property': 'py', + 'Name.Label': 'nl', + 'Name.Namespace': 'nn', + 'Name.Other': 'nx', + 'Name.Tag': 'nt', + 'Name.Variable': 'nv', + 'Name.Variable.Class': 'vc', + 'Name.Variable.Global': 'vg', + 'Name.Variable.Instance': 'vi', + + 'Literal': 'l', + 'Literal.Date': 'ld', + + 'String': 's', + 'String.Backtick': 'sb', + 'String.Char': 'sc', + 'String.Doc': 'sd', + 'String.Double': 's2', + 'String.Escape': 'se', + 'String.Heredoc': 'sh', + 'String.Interpol': 'si', + 'String.Other': 'sx', + 'String.Regex': 'sr', + 'String.Single': 's1', + 'String.Symbol': 'ss', + + 'Number': 'm', + 'Number.Float': 'mf', + 'Number.Hex': 'mh', + 'Number.Integer': 'mi', + 'Number.Integer.Long': 'il', + 'Number.Oct': 'mo', + + 'Operator': 'o', + 'Operator.Word': 'ow', + + 'Punctuation': 'p', + + 'Comment': 'c', + 'Comment.Multiline': 'cm', + 'Comment.Preproc': 'cp', + 'Comment.Single': 'c1', + 'Comment.Special': 'cs', + + 'Generic': 'g', + 'Generic.Deleted': 'gd', + 'Generic.Emph': 'ge', + 'Generic.Error': 'gr', + 'Generic.Heading': 'gh', + 'Generic.Inserted': 'gi', + 'Generic.Output': 'go', + 'Generic.Prompt': 'gp', + 'Generic.Strong': 'gs', + 'Generic.Subheading': 'gu', + 'Generic.Traceback': 'gt' +} + + +function makeTokenProducer(regexData, flags) { + var data = {}; + var procCasesMap = {}; + + // topological sort of state dependencies + var statesToProcess = []; + var sortedStates = []; + var sortedStatesMap = {}; + for(var state in regexData) statesToProcess.push(state); + while (statesToProcess.length > 0) { + var state = statesToProcess.shift(); + var stateReady = true; + forEach(regexData[state], function (c) { + if ((typeof c) == "object" && c.include) { + var otherState = c.include; + if (/!$/.exec(otherState)) { + otherState = otherState.substring(0, otherState.length-1); + } + if (! sortedStatesMap[otherState]) { + stateReady = false; + return true; + } + } + }); + if (stateReady) { + sortedStates.push(state); + sortedStatesMap[state] = true; + } + else { + // move to end of queue + statesToProcess.push(state); + } + } + + forEach(sortedStates, function(state) { + var cases = regexData[state]; + var procCases = []; + forEach(cases, function (c) { + if ((typeof c) == "object" && c.include) { + var otherState = c.include; + var isBang = false; + if (/!$/.exec(otherState)) { + // "bang" include, returns to other state + otherState = otherState.substring(0, otherState.length-1); + isBang = true; + } + forEach(procCasesMap[otherState], function (d) { + var dd = [d[0], d[1], d[2]]; + if (isBang) { + if (! (d[2] && d[2][0] && d[2][0].indexOf('#pop') != 0)) { + dd[2] = ['#pop', otherState].concat(d[2] || []); + } + } + procCases.push(dd); + }); + } + else procCases.push(c); + }); + procCasesMap[state] = procCases; + data[state] = { + switcher: makeRegexSwitch(map(procCases, function(x) { return x[0]; }), flags), + tokenTypes: map(procCases, function(x) { return x[1]; }), + stateEffects: map(procCases, function(y) { + var x = y[2]; + if (!x) return []; + if (isArray(x)) return x; + return [x]; + }) + } + }); + + // mutates stateStack, calls tokenFunc on each new token in order, returns new index + return function(string, startIndex, stateStack, tokenFunc) { + var stateBefore = stateStack.join('/'); + + while (true) { // loop until non-zero-length token + var stateData = data[stateStack[stateStack.length-1]]; + var switcherResult = stateData.switcher(string, startIndex); + var whichCase = switcherResult.whichCase; + var regexResult = switcherResult.result; + var tokenTypes, stateEffects; + if (whichCase < 0) { + tokenTypes = 'Error'; + stateEffects = null; + } + else { + tokenTypes = stateData.tokenTypes[whichCase]; + stateEffects = stateData.stateEffects[whichCase]; + } + + if (stateEffects) { + forEach(stateEffects, function (se) { + if (se === '#pop') stateStack.pop(); + else if (se === '#popall') { + while (stateStack.length > 0) stateStack.pop(); + } + else stateStack.push(se); + }); + } + var stateAfter = stateStack.join('/'); + + if (regexResult[0].length > 0) { + if ((typeof tokenTypes) === "object" && tokenTypes.bygroups) { + var types = tokenTypes.bygroups; + forEach(types, function (t,i) { + var tkn = { width:regexResult[i+1].length, type:t }; + if (i == 0) tkn.stateBefore = stateBefore; + if (i == (types.length-1)) tkn.stateAfter = stateAfter; + tokenFunc(tkn); + }); + } + else { + tokenFunc({ width:regexResult[0].length, type:tokenTypes, + stateBefore:stateBefore, stateAfter:stateAfter }); + } + return startIndex + regexResult[0].length; + } + } + } +} + +function makeSimpleLexer(tokenProducer) { + function lexString(str, tokenFunc) { + var state = ['root']; + var idx = 0; + while (idx < str.length) { + var i = idx; + idx = tokenProducer(str, idx, state, function (tkn) { + tokenFunc(str.substr(i, tkn.width), tkn.type); + i += tkn.width; + }); + } + } + function lexAsLines(str, tokenFunc, newLineFunc) { + str += "\n"; + var nextNewline = str.indexOf('\n'); + var curIndex = 0; + + lexString(str, function(txt, typ) { + var wid = txt.length; + var widthLeft = wid; + while (widthLeft > 0 && curIndex + wid > nextNewline) { + var w = nextNewline - curIndex; + if (w > 0) { + tokenFunc(str.substr(curIndex, w), typ); + } + curIndex += (w+1); + widthLeft -= (w+1); + if (curIndex < str.length) { + newLineFunc(); + nextNewline = str.indexOf("\n", curIndex); + } + } + if (widthLeft > 0) { + tokenFunc(str.substr(curIndex, widthLeft), typ); + curIndex += widthLeft; + } + }); + } + return {lexString:lexString, lexAsLines:lexAsLines}; +} + +var txtTokenProducer = makeTokenProducer( + { + 'root': [ + [/.*?\n/, 'Text'], + [/.+/, 'Text'] + ] + }, 's'); + +var jsTokenProducer = makeTokenProducer( + { + 'root': [ + [/\/\*[^\w\n]+appjet:version[^\w\n]+[0-9.]+[^\w\n]+\*\/[^\w\n]*(?=\n)/, + 'Comment.Special', 'main'], + [/(?:)/m, 'Text', ['main', 'regex-ready', 'linestart']] + ], + 'whitespace' : [ + [/\n/, 'Text', 'linestart'], + [/[^\S\n]+/, 'Text'], + [/\/\*/, 'Comment', 'longcomment'] + ], + 'common' : [ + {include:'whitespace'}, + [/\"/, 'String.Double', 'dstr'], + [/\'/, 'String.Single', 'sstr'] + ], + 'regex-ready' : [ + {include:'whitespace'}, + [/\/(?:[^[\\\n\/]|\\.|\[\^?]?(?:[^\\\]\n]|\\.)+\]?)+\/[gim]*/, 'String.Regex'], + [/(?:)/m, 'Text', '#pop'] + ], + 'main': [ + [/\"\"\"/, 'String.Doc', 'mstr'], + {include:"common"}, + [/<!--/, 'Comment'], + [/\/\/.*?(?=\n)/, 'Comment'], + [/[\{\}\[\]\(;]/, 'Punctuation', 'regex-ready'], + [/[\).]/, 'Punctuation'], + [/[~\^\*!%&<>\|=:,\/?\\]/, 'Operator', 'regex-ready'], + [/[+-]/, 'Operator'], + ['(import|break|case|catch|const|continue|default|delete|do|else|'+ + 'export|for|function|if|in|instanceof|label|new|return|switch|this|'+ + 'throw|try|typeof|var|void|while|with|abstract|boolean|byte|catch|char|'+ + 'class|const|debugger|double|enum|extends|final|finally|float|goto|implements|'+ + 'int|interface|long|native|package|private|protected|public|short|static|super|'+ + 'synchronized|throws|transient|volatile|let|yield)\\b', 'Keyword'], + [/(true|false|null|NaN|Infinity|undefined)\b/, 'Keyword.Constant'], + [/[$a-zA-Z_][a-zA-Z0-9_]*/, 'Name.Other'], + [/[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?/, 'Number.Float'], + [/0x[0-9a-f]+/, 'Number.Hex'], + [/[0-9]+/, 'Number.Integer'] + ], + 'csscommon': [ // common outside of style rule brackets + {include:'common'}, + [/\{/, 'Punctuation', 'csscontent'], + [/\:[a-zA-Z0-9_-]+/, 'Name.Decorator'], + [/\.[a-zA-Z0-9_-]+/, 'Name.Class'], + [/\#[a-zA-Z0-9_-]+/, 'Name.Function'], + [/[a-zA-Z0-9_-]+/, 'Name.Tag'], + [/[~\^\*!%&\[\]\(\)<>\|+=@:;,.\/?-]/, 'Operator'] + ], + 'cssmain': [ + [/(@media)([^\S\n]+)(\w+)([^\S\n]*)(\{)/, {bygroups:['Keyword', 'Text', 'String', + 'Text', 'Punctuation']}, 'cssmedia'], + {include:'csscommon'} + ], + 'cssmedia': [ + {include:'csscommon'}, + [/\}/, 'Punctuation', '#pop'] + ], + 'csscontent': [ + {include:'common'}, + [/\}/, 'Punctuation', '#pop'], + [/url\(.*?\)/, 'String.Other'], + ['(azimuth|background-attachment|background-color|'+ + 'background-image|background-position|background-repeat|'+ + 'background|border-bottom-color|border-bottom-style|'+ + 'border-bottom-width|border-left-color|border-left-style|'+ + 'border-left-width|border-right|border-right-color|'+ + 'border-right-style|border-right-width|border-top-color|'+ + 'border-top-style|border-top-width|border-bottom|'+ + 'border-collapse|border-left|border-width|border-color|'+ + 'border-spacing|border-style|border-top|border|caption-side|'+ + 'clear|clip|color|content|counter-increment|counter-reset|'+ + 'cue-after|cue-before|cue|cursor|direction|display|'+ + 'elevation|empty-cells|float|font-family|font-size|'+ + 'font-size-adjust|font-stretch|font-style|font-variant|'+ + 'font-weight|font|height|letter-spacing|line-height|'+ + 'list-style-type|list-style-image|list-style-position|'+ + 'list-style|margin-bottom|margin-left|margin-right|'+ + 'margin-top|margin|marker-offset|marks|max-height|max-width|'+ + 'min-height|min-width|opacity|orphans|outline|outline-color|'+ + 'outline-style|outline-width|overflow|padding-bottom|'+ + 'padding-left|padding-right|padding-top|padding|page|'+ + 'page-break-after|page-break-before|page-break-inside|'+ + 'pause-after|pause-before|pause|pitch|pitch-range|'+ + 'play-during|position|quotes|richness|right|size|'+ + 'speak-header|speak-numeral|speak-punctuation|speak|'+ + 'speech-rate|stress|table-layout|text-align|text-decoration|'+ + 'text-indent|text-shadow|text-transform|top|unicode-bidi|'+ + 'vertical-align|visibility|voice-family|volume|white-space|'+ + 'widows|width|word-spacing|z-index|bottom|left|'+ + 'above|absolute|always|armenian|aural|auto|avoid|baseline|'+ + 'behind|below|bidi-override|blink|block|bold|bolder|both|'+ + 'capitalize|center-left|center-right|center|circle|'+ + 'cjk-ideographic|close-quote|collapse|condensed|continuous|'+ + 'crop|crosshair|cross|cursive|dashed|decimal-leading-zero|'+ + 'decimal|default|digits|disc|dotted|double|e-resize|embed|'+ + 'extra-condensed|extra-expanded|expanded|fantasy|far-left|'+ + 'far-right|faster|fast|fixed|georgian|groove|hebrew|help|'+ + 'hidden|hide|higher|high|hiragana-iroha|hiragana|icon|'+ + 'inherit|inline-table|inline|inset|inside|invert|italic|'+ + 'justify|katakana-iroha|katakana|landscape|larger|large|'+ + 'left-side|leftwards|level|lighter|line-through|list-item|'+ + 'loud|lower-alpha|lower-greek|lower-roman|lowercase|ltr|'+ + 'lower|low|medium|message-box|middle|mix|monospace|'+ + 'n-resize|narrower|ne-resize|no-close-quote|no-open-quote|'+ + 'no-repeat|none|normal|nowrap|nw-resize|oblique|once|'+ + 'open-quote|outset|outside|overline|pointer|portrait|px|'+ + 'relative|repeat-x|repeat-y|repeat|rgb|ridge|right-side|'+ + 'rightwards|s-resize|sans-serif|scroll|se-resize|'+ + 'semi-condensed|semi-expanded|separate|serif|show|silent|'+ + 'slow|slower|small-caps|small-caption|smaller|soft|solid|'+ + 'spell-out|square|static|status-bar|super|sw-resize|'+ + 'table-caption|table-cell|table-column|table-column-group|'+ + 'table-footer-group|table-header-group|table-row|'+ + 'table-row-group|text|text-bottom|text-top|thick|thin|'+ + 'transparent|ultra-condensed|ultra-expanded|underline|'+ + 'upper-alpha|upper-latin|upper-roman|uppercase|url|'+ + 'visible|w-resize|wait|wider|x-fast|x-high|x-large|x-loud|'+ + 'x-low|x-small|x-soft|xx-large|xx-small|yes)\\b', 'Keyword'], + ['(indigo|gold|firebrick|indianred|yellow|darkolivegreen|'+ + 'darkseagreen|mediumvioletred|mediumorchid|chartreuse|'+ + 'mediumslateblue|black|springgreen|crimson|lightsalmon|brown|'+ + 'turquoise|olivedrab|cyan|silver|skyblue|gray|darkturquoise|'+ + 'goldenrod|darkgreen|darkviolet|darkgray|lightpink|teal|'+ + 'darkmagenta|lightgoldenrodyellow|lavender|yellowgreen|thistle|'+ + 'violet|navy|orchid|blue|ghostwhite|honeydew|cornflowerblue|'+ + 'darkblue|darkkhaki|mediumpurple|cornsilk|red|bisque|slategray|'+ + 'darkcyan|khaki|wheat|deepskyblue|darkred|steelblue|aliceblue|'+ + 'gainsboro|mediumturquoise|floralwhite|coral|purple|lightgrey|'+ + 'lightcyan|darksalmon|beige|azure|lightsteelblue|oldlace|'+ + 'greenyellow|royalblue|lightseagreen|mistyrose|sienna|'+ + 'lightcoral|orangered|navajowhite|lime|palegreen|burlywood|'+ + 'seashell|mediumspringgreen|fuchsia|papayawhip|blanchedalmond|'+ + 'peru|aquamarine|white|darkslategray|ivory|dodgerblue|'+ + 'lemonchiffon|chocolate|orange|forestgreen|slateblue|olive|'+ + 'mintcream|antiquewhite|darkorange|cadetblue|moccasin|'+ + 'limegreen|saddlebrown|darkslateblue|lightskyblue|deeppink|'+ + 'plum|aqua|darkgoldenrod|maroon|sandybrown|magenta|tan|'+ + 'rosybrown|pink|lightblue|palevioletred|mediumseagreen|'+ + 'dimgray|powderblue|seagreen|snow|mediumblue|midnightblue|'+ + 'paleturquoise|palegoldenrod|whitesmoke|darkorchid|salmon|'+ + 'lightslategray|lawngreen|lightgreen|tomato|hotpink|'+ + 'lightyellow|lavenderblush|linen|mediumaquamarine|green|'+ + 'blueviolet|peachpuff)\\b', 'Name.Builtin'], + [/\!important/, 'Comment.Preproc'], + [/\#[a-zA-Z0-9]{1,6}/, 'Number'], + [/[\.-]?[0-9]*[\.]?[0-9]+(em|px|\%|pt|pc|in|mm|cm|ex)/, 'Number'], + [/-?[0-9]+/, 'Number'], + [/[~\^\*!%&<>\|+=@:,.\/?-]+/, 'Operator'], + [/[\[\]();]+/, 'Punctuation'], + [/[a-zA-Z][a-zA-Z0-9]+/, 'Name'] + ], + 'linestart': [ + [/\/\*[^\w\n]+appjet:css[^\w\n]+\*\/[^\w\n]*(?=\n)/, 'Comment.Special', + ['#popall', 'root', 'cssmain']], + [/\/\*[^\w\n]+appjet:(\w+)[^\w\n]+\*\/[^\w\n]*(?=\n)/, 'Comment.Special', + ['#popall', 'root', 'main', 'regex-ready']], + [/(?:)/m, 'Text', '#pop'] + ], + 'dstr': [ + [/\"/, 'String.Double', '#pop'], + [/(?=\n)/m, 'String.Double', '#pop'], + [/(\\\\|\\\"|[^\"\n])+/, 'String.Double'] + ], + 'sstr': [ + [/\'/, 'String.Single', '#pop'], + [/(?=\n)/m, 'String.Single', '#pop'], + [/(\\\\|\\\'|[^\'\n])+/, 'String.Single'] + ], + 'longcomment': [ + [/\*\//, 'Comment', '#pop'], + [/\n/, 'Comment'], + [/.+?(?:\n|(?=\*\/))/, 'Comment'] + ], + 'mstr': [ + [/(\\\"\"\"|\n)/, 'String.Doc'], + [/\"\"\"/, 'String.Doc', '#pop'], + [/.+?(?=\\"""|"""|\n)/, 'String.Doc'] + ] + } +); + +function escapeHTML(s) { + var re = /[&<>\'\" ]/g; + if (! re.MAP) { + // persisted across function calls! + re.MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + ' ': ' ' + }; + } + return s.replace(re, function(c) { return re.MAP[c]; }); +} + +var simpleLexer = makeSimpleLexer(jsTokenProducer); + +function codeStringToHTML(codeString) { + var atLineStart = true; + var html = []; + function tokenFunc(txt, type) { + var cls = tokenClasses[type]; + if (cls) html.push('<tt class="',tokenClasses[type],'">'); + else html.push('<tt>'); + html.push(escapeHTML(txt),'</tt>'); + atLineStart = false; + } + function newLineFunc() { + html.push('<br/>\n'); + atLineStart = true; + } + simpleLexer.lexAsLines(codeString, tokenFunc, newLineFunc); + if (atLineStart) html.push('<br/>\n'); + return html.join(''); +} + +/* ========== Incremental Lexer for ACE ========== */ + +function makeIncrementalLexer(tokenProducer) { + + var tokens = newSkipList(); + var buffer = ""; + var nextId = 1; + var dirtyTokenKeys = []; + var uncoloredRanges = []; + + //top.dbg_uncoloredRanges = function() { return uncoloredRanges; } + //top.dbg_dirtyTokenKeys = function() { return dirtyTokenKeys; } + + function mergeRangesIfTouching(a, b) { + // if a = [a0,a1] and b = [b0,b1] are overlapping or touching, return a single merged range + // else return null + var a0 = a[0], a1 = a[1], b0 = b[0], b1 = b[1]; + if (a1 < b0) return null; + if (b1 < a0) return null; + var c0 = ((a0 < b0) ? a0 : b0); + var c1 = ((a1 > b1) ? a1 : b1); + return [c0,c1]; + } + + function addUncoloredRange(rng) { + // shouldn't this merge existing ranges if the new range overlaps multiple ones? + var done = false; + forEach(uncoloredRanges, function (x, i) { + var merged = mergeRangesIfTouching(x, rng); + if (merged) { + uncoloredRanges[i] = merged; + done = true; + return true; + } + }); + if (! done) { + uncoloredRanges.push(rng); + } + } + + function removeUncoloredRange(rng) { + var i = uncoloredRanges.length-1; + while (i >= 0) { + removeUncoloredRangeFrom(rng, i); + i--; + } + } + + function removeUncoloredRangeFrom(rangeToRemove, containingRangeIndex) { + var idx = containingRangeIndex; + var cont = uncoloredRanges[idx]; + var rem0 = rangeToRemove[0], rem1 = rangeToRemove[1]; + // limit to containing range + if (rem0 < cont[0]) rem0 = cont[0]; + if (rem1 > cont[1]) rem1 = cont[1]; + if (rem1 <= rem0) return; + // splice out uncoloredRanes[containingRangeIndex] for 0, 1, or 2 ranges + uncoloredRanges.splice(idx, 1); + if (cont[0] < rem0) + uncoloredRanges.splice(idx, 0, [cont[0], rem0]); + if (rem1 < cont[1]) + uncoloredRanges.splice(idx, 0, [rem1, cont[1]]); + } + + function prepareTokens(tokenArray) { + forEach(tokenArray, function (t) { + t.key = "$"+(nextId++); + }); + return tokenArray; + } + + function roundBackToTokenBoundary(charOffset) { + return tokens.indexOfOffset(charOffset); + } + function roundForwardToTokenBoundary(charOffset) { + var tokenEnd; + if (charOffset == tokens.totalWidth()) + tokenEnd = tokens.length(); + else { + var endToken = tokens.keyAtOffset(charOffset); + tokenEnd = tokens.indexOfKey(endToken); + // round up to nearest token boundary + if (charOffset > tokens.offsetOfKey(endToken)) { + tokenEnd++; + } + } + return tokenEnd; + } + + // findLexingStartPoint and findLexingEndPoint take a character boundary + // (0 .. buffer.length) and return a token boundary (0 .. tokens.length()) + // that, if not at the document edge, is such that the next token outside + // the boundary has a pre/post lexing state associated with it (i.e is not + // a dirty-region token or in the middle of a multi-token lexing rule). + + function findLexingStartPoint(startChar) { + if (tokens.length() == 0) return 0; + var tokenStart = roundBackToTokenBoundary(startChar); + // expand to not break up a series of tokens from the same + // lexing rule, and to include dirty regions + if (tokenStart > 0) { + var tokenBefore = tokens.atIndex(tokenStart - 1); + while (tokenBefore && (! tokenBefore.stateAfter)) { + tokenStart--; + tokenBefore = tokens.prev(tokenBefore); + } + } + return tokenStart; + } + + function findLexingEndPoint(endChar) { + if (tokens.length() == 0) return 0; + var tokenEnd = roundForwardToTokenBoundary(endChar); + // expand to not break up a series of tokens from the same + // lexing rule, and to include dirty regions + if (tokenEnd < tokens.length()) { + var tokenAfter = tokens.atIndex(tokenEnd); + while (tokenAfter && (! tokenAfter.stateBefore)) { + tokenEnd++; + tokenAfter = tokens.next(tokenAfter); + } + } + return tokenEnd; + } + + function updateBuffer(newBuffer, spliceStart, charsRemoved, charsAdded) { + buffer = newBuffer; + + // back up to new line + if (spliceStart > 0) { + var newStart = buffer.lastIndexOf('\n', spliceStart-1) + 1; + var charsBack = spliceStart - newStart; + spliceStart -= charsBack; + charsRemoved += charsBack; + charsAdded += charsBack; + } + // expand to lexing points + var tokenRangeStart = findLexingStartPoint(spliceStart); + var tokenRangeEnd = findLexingEndPoint(spliceStart + charsRemoved); + + var dirtyWidth = 0; + // make sure to mark at least one token dirty so that deletions correctly cause + // rehighlighting; in practice doesn't come up often except when an entire line + // is cleanly deleted, like deleting a blank line (which doesn't usually affect highlighting) + while (dirtyWidth == 0) { + var curStart = tokens.offsetOfIndex(tokenRangeStart); + var curEnd = tokens.offsetOfIndex(tokenRangeEnd); + dirtyWidth = (curEnd - curStart) + (charsAdded - charsRemoved); + if (dirtyWidth == 0) { + if (curEnd >= tokens.totalWidth()) break; + tokenRangeEnd = findLexingEndPoint(curEnd+1); + } + } + + var dirtyTokens = []; // 0 or 1 of them + if (dirtyWidth > 0) { + dirtyTokens.push({ width: dirtyWidth, type: 'Dirty' }); + } + + //console.log("%d, %d, %d, %d", charsRemoved, charsAdded, + //(curEnd - curStart), dirtyWidth); + + tokens.splice(tokenRangeStart, tokenRangeEnd - tokenRangeStart, + prepareTokens(dirtyTokens)); + + if (tokens.totalWidth() != buffer.length) { + console.error("updateBuffer: Bad total token width: "+ + tokens.totalWidth()+" not "+buffer.length); + } + + forEach(dirtyTokens, function (t) { dirtyTokenKeys.push(t.key); }); + dirtyTokenKeys = filter(dirtyTokenKeys, function (k) { return tokens.containsKey(k); }); + //console.log("after update: %s", toSource(tokens.slice())); + + function applySpliceToIndex(i) { + if (i <= spliceStart) return i; + if (i >= (spliceStart + charsRemoved)) return i + charsAdded - charsRemoved; + return spliceStart; + } + for(var i=uncoloredRanges.length-1; i>=0; i--) { + var r = uncoloredRanges[i]; + r[0] = applySpliceToIndex(r[0]); + r[1] = applySpliceToIndex(r[1]); + if (r[1] <= r[0]) uncoloredRanges.splice(i, 1); + } + } + + function processDirtyToken(dirtyToken, isTimeUp, stopAtChar) { + + //console.time("lexing"); + //var p = PROFILER("lex", false); + var stateStack; + if (! tokens.prev(dirtyToken)) stateStack = ['root']; + else stateStack = tokens.prev(dirtyToken).stateAfter.split('/'); + var newTokens = []; + var dirtyTokenIndex = tokens.indexOfEntry(dirtyToken); + var tokenCount = 0; + var startTime = (new Date()).getTime(); + var stopBasedOnChar = (typeof(stopAtChar) == "number"); + //p.mark("tokenize"); + + var curOffset = tokens.offsetOfEntry(dirtyToken); + var startedOffset = curOffset; + var oldToken = dirtyToken; + var oldTokenOffset = curOffset; + var done = false; + + while ((! done) && (! isTimeUp()) && (! (stopBasedOnChar && curOffset >= stopAtChar))) { + curOffset = tokenProducer(buffer, curOffset, stateStack, + function (t) { newTokens.push(t); }); + while (oldToken && (oldTokenOffset + oldToken.width <= curOffset)) { + oldTokenOffset += oldToken.width; + oldToken = tokens.next(oldToken); + } + if (curOffset == tokens.totalWidth()) { + // hit the end + done = true; + } + else if (oldTokenOffset == curOffset) { + // at a token boundary, the beginning of oldTokenOffset + if (stateStack.join('/') === oldToken.stateBefore) { + // state matches up, we can stop + done = true; + } + } + } + + var endedOffset = curOffset; + var dist = endedOffset - startedOffset; + var tokensToRemove; + var newDirtyToken; + if (dist < dirtyToken.width) { + tokens.setEntryWidth(dirtyToken, dirtyToken.width - dist); + tokensToRemove = 0; + newDirtyToken = dirtyToken; + } + else { + var nextLexingPoint = findLexingEndPoint(endedOffset); + var lexingPointChar = tokens.offsetOfIndex(nextLexingPoint); + if (lexingPointChar == endedOffset && (! done) && endedOffset < tokens.totalWidth()) { + // happened to stop at token boundary before end, but not done lexing, + // so make next token dirty + nextLexingPoint = findLexingEndPoint(endedOffset+1); + lexingPointChar = tokens.offsetOfIndex(nextLexingPoint); + } + var dirtyCharsLeft = lexingPointChar - endedOffset; + if (dirtyCharsLeft > 0) { + newDirtyToken = { width: dirtyCharsLeft, type: 'Dirty' }; + newTokens.push(newDirtyToken); + } + tokensToRemove = nextLexingPoint - dirtyTokenIndex; + } + + //p.mark("prepare"); + prepareTokens(newTokens); + //p.mark("remove"); + tokens.splice(dirtyTokenIndex, tokensToRemove, []); + //p.mark("insert"); + tokens.splice(dirtyTokenIndex, 0, newTokens); + if (tokens.totalWidth() != buffer.length) + console.error("processDirtyToken: Bad total token width: "+ + tokens.totalWidth()+" not "+buffer.length); + //p.end(); + + addUncoloredRange([startedOffset, endedOffset]); + + //console.log("processed chars %d to %d", startedOffset, endedOffset); + //console.timeEnd("lexing"); + + return (newDirtyToken && newDirtyToken.key); + } + + function lexSomeDirty(filter, isTimeUp) { + var newDirtyTokenKeys = []; + + forEach(dirtyTokenKeys, function (dirtyKey) { + if (! tokens.containsKey(dirtyKey)) return; + var dirtyToken = tokens.atKey(dirtyKey); + var filterResult; + if ((! isTimeUp()) && ((filterResult = filter(dirtyToken)))) { + var stopAtChar; + if ((typeof filterResult) == "object" && (typeof filterResult.stopAtChar) == "number") { + stopAtChar = filterResult.stopAtChar; + } + var tkn = processDirtyToken(dirtyToken, isTimeUp, filterResult.stopAtChar); + if (tkn) newDirtyTokenKeys.push(tkn); + } + else { + // leave the token behind + newDirtyTokenKeys.push(dirtyKey); + } + + if (tokens.totalWidth() != buffer.length) + console.error("Bad total token width: "+tokens.totalWidth()+" not "+buffer.length); + + }); + + dirtyTokenKeys = newDirtyTokenKeys; + } + + function lexCharRange(charRange, isTimeUp) { + //var startTime = (new Date()).getTime(); + //function isTimeUp() { return ((new Date()).getTime() - startTime) > timeLimit; } + + if (isTimeUp()) return; + + lexSomeDirty(function (dirtyToken) { + var start = tokens.offsetOfEntry(dirtyToken); + var end = start + dirtyToken.width; + if (end <= charRange[0]) return false; + if (start >= charRange[1]) return false; + //console.log("tokenStart: %d, tokenEnd: %d, visStart: %d, visEnd: %d", + //start, end, charRange[0], charRange[1]); + var result = {}; + if (charRange[1] < end) { + result.stopAtChar = charRange[1]; + } + return result; + }, isTimeUp); + + //if (isTimeUp()) return; + + /* + // highlight the visible area + var i = uncoloredRanges.length-1; + // iterate backwards because we change the array + while (i >= 0) { + var rng = uncoloredRanges[i]; + var start = rng[0], end = rng[1]; + if (start < viewRange[0]) start = viewRange[0]; + if (end > viewRange[1]) end = viewRange[1]; + if (end > start) { + var charsRecolored = recolorFunc(start, end-start, + isTimeUp, getSpansForRange); + removeUncoloredSubrange([start, start+charsRecolored], i); + } + if (isTimeUp()) break; + i--; + }*/ + } + + function tokenToString(tkn) { + return toSource({width:tkn.width, type:tkn.type, stateBefore:tkn.stateBefore, stateAfter:tkn.stateAfter}); + } + + // Calls func(startChar, endChar) on each range of characters that needs to be colored + // in the DOM, based on calls to getSpansForRange (which removes chars from consideration) + // and lexCharRange (which calculates new colors and adds chars for consideration). + // There are usually relatively few uncolored ranges, each of which may be many lines, + // even the whole document. + // func must return true iff any tokens are accessed through getSpansForRange during + // the call. func should not do new lexing. + function forEachUncoloredRange(func, isTimeUp) { + var i = 0; + // uncoloredRanges will change during this function! + // Terminates is time runs out, whole document is colored, + // or the func "passes" on all ranges by returning false. + while (i < uncoloredRanges.length && ! isTimeUp()) { + var rng = uncoloredRanges[i]; + var returnVal = func(rng[0], rng[1], isTimeUp); + if (returnVal) { + // func did something, uncolored ranges may have changed around + i = 0; + } + else { + i++; + } + } + } + + // Like forEachUncoloredRange, but "cropped" to the char range given. For example, + // if no "uncolored ranges" extend by a non-zero amount into the char range, + // func will never be called. + function forEachUncoloredSubrange(startChar, endChar, func, isTimeUp) { + forEachUncoloredRange(function (s, e, isTimeUp2) { + if (s < startChar) s = startChar; + if (e > endChar) e = endChar; + if (e > s) { + return func(s, e, isTimeUp2); + } + return false; + }, isTimeUp); + } + + // This function takes note of what it's passed, and assumes that part of the + // DOM has been taken care of (unless justPeek). + // The "func" takes arguments tokenWidth and tokenClass, and is called on each + // token in the range, with the widths adding up to the range size. + function getSpansForRange(startChar, endChar, func, justPeek) { + + if (startChar == endChar) return; + + var startToken = tokens.atOffset(startChar); + var startTokenStart = tokens.offsetOfEntry(startToken); + var curOffset = startChar; + var curToken = startToken; + while (curOffset < endChar) { + var spanEnd; + if (curToken === startToken) { + spanEnd = startTokenStart + startToken.width; + } + else { + spanEnd = curOffset + curToken.width; + } + if (spanEnd > endChar) spanEnd = endChar; + if (spanEnd > curOffset) { + func(spanEnd - curOffset, tokenClasses[curToken.type]); + } + curOffset = spanEnd; + curToken = tokens.next(curToken); + } + + if (! justPeek) removeUncoloredRange([startChar, endChar]); + } + + function markRangeUncolored(start, end) { + addUncoloredRange([start, end]); + } + + return { + updateBuffer: updateBuffer, + lexCharRange: lexCharRange, + getSpansForRange: getSpansForRange, + forEachUncoloredSubrange: forEachUncoloredSubrange, + markRangeUncolored: markRangeUncolored + }; +} + +/* ========== End Incremental Lexer ========== */ + +tokenProds = {js: jsTokenProducer, txt: txtTokenProducer}; + +function getTokenProducer(type) { + return tokenProds[type || 'txt'] || tokenProds['txt']; +} + +function getIncrementalLexer(type) { + return makeIncrementalLexer(getTokenProducer(type)); +} +function getSimpleLexer(type) { + return makeSimpleLexer(getTokenProducer(type)); +} + +return {getIncrementalLexer:getIncrementalLexer, getSimpleLexer:getSimpleLexer, + codeStringToHTML:codeStringToHTML}; + +})(); |