/com.hangum.db.browser.rap.core/orion/orion/editor/textMateStyler.js
JavaScript | 1322 lines | 968 code | 50 blank | 304 comment | 243 complexity | 6ecd6f6fc5bd4c98b6b84e622e99dd6e MD5 | raw file
- /*******************************************************************************
- * Copyright (c) 2011 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License v1.0
- * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
- * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
- /*jslint regexp:false laxbreak:true*/
- /*global define dojo window*/
- var orion = orion || {};
- orion.editor = orion.editor || {};
- /**
- * A styler that does nothing, but can be extended by concrete stylers. Extenders can call
- * {@link orion.editor.AbstractStyler.extend} and provide their own {@link #_onSelection},
- * {@link #_onModelChanged}, {@link #_onDestroy} and {@link #_onLineStyle} methods.
- * @class orion.editor.AbstractStyler
- */
- orion.editor.AbstractStyler = (function() {
- /** @inner */
- function AbstractStyler() {
- }
- AbstractStyler.prototype = /** @lends orion.editor.AbstractStyler.prototype */ {
- /**
- * Initializes this styler with a textView. Extenders <b>must</b> call this from their constructor.
- * @param {orion.textview.TextView} textView
- */
- initialize: function(textView) {
- this.textView = textView;
-
- textView.addEventListener("Selection", this, this._onSelection);
- textView.addEventListener("ModelChanged", this, this._onModelChanged);
- textView.addEventListener("Destroy", this, this._onDestroy);
- textView.addEventListener("LineStyle", this, this._onLineStyle);
- textView.redrawLines();
- },
-
- /**
- * Destroys this styler and removes all listeners. Called by the editor.
- */
- destroy: function() {
- if (this.textView) {
- this.textView.removeEventListener("Selection", this, this._onSelection);
- this.textView.removeEventListener("ModelChanged", this, this._onModelChanged);
- this.textView.removeEventListener("Destroy", this, this._onDestroy);
- this.textView.removeEventListener("LineStyle", this, this._onLineStyle);
- this.textView = null;
- }
- },
-
- /** To be overridden by subclass.
- * @public
- */
- _onSelection: function(/**eclipse.SelectionEvent*/ e) {},
-
- /** To be overridden by subclass.
- * @public
- */
- _onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) {},
-
- /** To be overridden by subclass.
- * @public
- */
- _onDestroy: function(/**eclipse.DestroyEvent*/ e) {},
-
- /** To be overridden by subclass.
- * @public
- */
- _onLineStyle: function(/**eclipse.LineStyleEvent*/ e) {}
- };
-
- return AbstractStyler;
- }());
- /**
- * Helper for extending AbstractStyler.
- * @methodOf orion.editor.AbstractStyler
- * @static
- * @param {Function} subCtor The constructor function for the subclass.
- * @param {Object} [proto] Object to be mixed into the subclass's prototype. This object can contain your
- * implementation of _onSelection, _onModelChanged, etc.
- * @see orion.editor.TextMateStyler for example usage.
- */
- orion.editor.AbstractStyler.extend = function(subCtor, proto) {
- if (typeof(subCtor) !== "function") { throw new Error("Function expected"); }
- subCtor.prototype = new orion.editor.AbstractStyler();
- subCtor.constructor = subCtor;
- for (var p in proto) {
- if (proto.hasOwnProperty(p)) { subCtor.prototype[p] = proto[p]; }
- }
- };
- orion.editor.RegexUtil = {
- // Rules to detect some unsupported Oniguruma features
- unsupported: [
- {regex: /\(\?[ims\-]:/, func: function(match) { return "option on/off for subexp"; }},
- {regex: /\(\?<([=!])/, func: function(match) { return (match[1] === "=") ? "lookbehind" : "negative lookbehind"; }},
- {regex: /\(\?>/, func: function(match) { return "atomic group"; }}
- ],
-
- /**
- * @param {String} str String giving a regular expression pattern from a TextMate grammar.
- * @param {String} [flags] [ismg]+
- * @returns {RegExp}
- */
- toRegExp: function(str) {
- function fail(feature, match) {
- throw new Error("Unsupported regex feature \"" + feature + "\": \"" + match[0] + "\" at index: "
- + match.index + " in " + match.input);
- }
- function getMatchingCloseParen(str, start) {
- var depth = 0,
- len = str.length,
- xStop = -1;
- for (var i=start; i < len && xStop === -1; i++) {
- switch (str[i]) {
- case "\\":
- i += 1; // skip next char
- break;
- case "(":
- depth++;
- break;
- case ")":
- depth--;
- if (depth === 0) {
- xStop = i;
- }
- break;
- }
- }
- return xStop;
- }
- // Turns an extended regex into a normal one
- function normalize(/**String*/ str) {
- var result = "";
- var insideCharacterClass = false;
- var len = str.length;
- for (var i=0; i < len; ) {
- var chr = str[i];
- if (!insideCharacterClass && chr === "#") {
- // skip to eol
- while (i < len && chr !== "\r" && chr !== "\n") {
- chr = str[++i];
- }
- } else if (!insideCharacterClass && /\s/.test(chr)) {
- // skip whitespace
- while (i < len && /\s/.test(chr)) {
- chr = str[++i];
- }
- } else if (chr === "\\") {
- result += chr;
- if (!/\s/.test(str[i+1])) {
- result += str[i+1];
- i += 1;
- }
- i += 1;
- } else if (chr === "[") {
- insideCharacterClass = true;
- result += chr;
- i += 1;
- } else if (chr === "]") {
- insideCharacterClass = false;
- result += chr;
- i += 1;
- } else {
- result += chr;
- i += 1;
- }
- }
- return result;
- }
-
- var flags = "";
- var i;
-
- // Check for unsupported syntax
- for (i=0; i < this.unsupported.length; i++) {
- var match;
- if ((match = this.unsupported[i].regex.exec(str))) {
- fail(this.unsupported[i].func(match));
- }
- }
-
- // Deal with "x" flag (whitespace/comments)
- if (str.substring(0, 4) === "(?x)") {
- // Leading (?x) term (means "x" flag applies to entire regex)
- str = normalize(str.substring(4));
- } else if (str.substring(0, 4) === "(?x:") {
- // Regex wrapped in a (?x: ...) -- again "x" applies to entire regex
- var xStop = getMatchingCloseParen(str, 0);
- if (xStop < str.length-1) {
- throw new Error("Only a (?x:) group that encloses the entire regex is supported: " + str);
- }
- str = normalize(str.substring(4, xStop));
- }
- // TODO: tolerate /(?iSubExp)/ -- eg. in PHP grammar (trickier)
- return new RegExp(str, flags);
- },
-
- hasBackReference: function(/**RegExp*/ regex) {
- return (/\\\d+/).test(regex.source);
- },
-
- /** @returns {RegExp} A regex made by substituting any backreferences in <tt>regex</tt> for the value of the property
- * in <tt>sub</tt> with the same name as the backreferenced group number. */
- getSubstitutedRegex: function(/**RegExp*/ regex, /**Object*/ sub, /**Boolean*/ escape) {
- escape = (typeof escape === "undefined") ? true : false;
- var exploded = regex.source.split(/(\\\d+)/g);
- var array = [];
- for (var i=0; i < exploded.length; i++) {
- var term = exploded[i];
- var backrefMatch = /\\(\d+)/.exec(term);
- if (backrefMatch) {
- var text = sub[backrefMatch[1]] || "";
- array.push(escape ? orion.editor.RegexUtil.escapeRegex(text) : text);
- } else {
- array.push(term);
- }
- }
- return new RegExp(array.join(""));
- },
-
- /** @returns {String} The input string with regex special characters escaped. */
- escapeRegex: function(/**String*/ str) {
- return str.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&");
- },
-
- /**
- * Builds a version of <tt>regex</tt> with every non-capturing term converted into a capturing group. This is a workaround
- * for JavaScript's lack of API to get the index at which a matched group begins in the input string.<p>
- * Using the "groupified" regex, we can sum the lengths of matches from <i>consuming groups</i> 1..n-1 to obtain the
- * starting index of group n. (A consuming group is a capturing group that is not inside a lookahead assertion).</p>
- * Example: groupify(/(a+)x+(b+)/) === /(a+)(x+)(b+)/<br />
- * Example: groupify(/(?:x+(a+))b+/) === /(?:(x+)(a+))(b+)/
- * @param {RegExp} regex The regex to groupify.
- * @param {Object} [backRefOld2NewMap] Optional. If provided, the backreference numbers in regex will be updated using the
- * properties of this object rather than the new group numbers of regex itself.
- * <ul><li>[0] {RegExp} The groupified version of the input regex.</li>
- * <li>[1] {Object} A map containing old-group to new-group info. Each property is a capturing group number of <tt>regex</tt>
- * and its value is the corresponding capturing group number of [0].</li>
- * <li>[2] {Object} A map indicating which capturing groups of [0] are also consuming groups. If a group number is found
- * as a property in this object, then it's a consuming group.</li></ul>
- */
- groupify: function(regex, backRefOld2NewMap) {
- var NON_CAPTURING = 1,
- CAPTURING = 2,
- LOOKAHEAD = 3,
- NEW_CAPTURING = 4;
- var src = regex.source,
- len = src.length;
- var groups = [],
- lookaheadDepth = 0,
- newGroups = [],
- oldGroupNumber = 1,
- newGroupNumber = 1;
- var result = [],
- old2New = {},
- consuming = {};
- for (var i=0; i < len; i++) {
- var curGroup = groups[groups.length-1];
- var chr = src[i];
- switch (chr) {
- case "(":
- // If we're in new capturing group, close it since ( signals end-of-term
- if (curGroup === NEW_CAPTURING) {
- groups.pop();
- result.push(")");
- newGroups[newGroups.length-1].end = i;
- }
- var peek2 = (i + 2 < len) ? (src[i+1] + "" + src[i+2]) : null;
- if (peek2 === "?:" || peek2 === "?=" || peek2 === "?!") {
- // Found non-capturing group or lookahead assertion. Note that we preserve non-capturing groups
- // as such, but any term inside them will become a new capturing group (unless it happens to
- // also be inside a lookahead).
- var groupType;
- if (peek2 === "?:") {
- groupType = NON_CAPTURING;
- } else {
- groupType = LOOKAHEAD;
- lookaheadDepth++;
- }
- groups.push(groupType);
- newGroups.push({ start: i, end: -1, type: groupType /*non capturing*/ });
- result.push(chr);
- result.push(peek2);
- i += peek2.length;
- } else {
- groups.push(CAPTURING);
- newGroups.push({ start: i, end: -1, type: CAPTURING, oldNum: oldGroupNumber, num: newGroupNumber });
- result.push(chr);
- if (lookaheadDepth === 0) {
- consuming[newGroupNumber] = null;
- }
- old2New[oldGroupNumber] = newGroupNumber;
- oldGroupNumber++;
- newGroupNumber++;
- }
- break;
- case ")":
- var group = groups.pop();
- if (group === LOOKAHEAD) { lookaheadDepth--; }
- newGroups[newGroups.length-1].end = i;
- result.push(chr);
- break;
- case "*":
- case "+":
- case "?":
- case "}":
- // Unary operator. If it's being applied to a capturing group, we need to add a new capturing group
- // enclosing the pair
- var op = chr;
- var prev = src[i-1],
- prevIndex = i-1;
- if (chr === "}") {
- for (var j=i-1; src[j] !== "{" && j >= 0; j--) {}
- prev = src[j-1];
- prevIndex = j-1;
- op = src.substring(j, i+1);
- }
- var lastGroup = newGroups[newGroups.length-1];
- if (prev === ")" && (lastGroup.type === CAPTURING || lastGroup.type === NEW_CAPTURING)) {
- // Shove in the new group's (, increment num/start in from [lastGroup.start .. end]
- result.splice(lastGroup.start, 0, "(");
- result.push(op);
- result.push(")");
- var newGroup = { start: lastGroup.start, end: result.length-1, type: NEW_CAPTURING, num: lastGroup.num };
- for (var k=0; k < newGroups.length; k++) {
- group = newGroups[k];
- if (group.type === CAPTURING || group.type === NEW_CAPTURING) {
- if (group.start >= lastGroup.start && group.end <= prevIndex) {
- group.start += 1;
- group.end += 1;
- group.num = group.num + 1;
- if (group.type === CAPTURING) {
- old2New[group.oldNum] = group.num;
- }
- }
- }
- }
- newGroups.push(newGroup);
- newGroupNumber++;
- break;
- } else {
- // Fallthrough to default
- }
- default:
- if (chr !== "|" && curGroup !== CAPTURING && curGroup !== NEW_CAPTURING) {
- // Not in a capturing group, so make a new one to hold this term.
- // Perf improvement: don't create the new group if we're inside a lookahead, since we don't
- // care about them (nothing inside a lookahead actually consumes input so we don't need it)
- if (lookaheadDepth === 0) {
- groups.push(NEW_CAPTURING);
- newGroups.push({ start: i, end: -1, type: NEW_CAPTURING, num: newGroupNumber });
- result.push("(");
- consuming[newGroupNumber] = null;
- newGroupNumber++;
- }
- }
- result.push(chr);
- if (chr === "\\") {
- var peek = src[i+1];
- // Eat next so following iteration doesn't think it's a real special character
- result.push(peek);
- i += 1;
- }
- break;
- }
- }
- while (groups.length) {
- // Close any remaining new capturing groups
- groups.pop();
- result.push(")");
- }
- var newRegex = new RegExp(result.join(""));
-
- // Update backreferences so they refer to the new group numbers. Use backRefOld2NewMap if provided
- var subst = {};
- backRefOld2NewMap = backRefOld2NewMap || old2New;
- for (var prop in backRefOld2NewMap) {
- if (backRefOld2NewMap.hasOwnProperty(prop)) {
- subst[prop] = "\\" + backRefOld2NewMap[prop];
- }
- }
- newRegex = this.getSubstitutedRegex(newRegex, subst, false);
-
- return [newRegex, old2New, consuming];
- },
-
- /** @returns {Boolean} True if the captures object assigns scope to a matching group other than "0". */
- complexCaptures: function(capturesObj) {
- if (!capturesObj) { return false; }
- for (var prop in capturesObj) {
- if (capturesObj.hasOwnProperty(prop)) {
- if (prop !== "0") {
- return true;
- }
- }
- }
- return false;
- }
- };
- /**
- * A styler that knows how to apply a limited subset of the TextMate grammar format to style a line.<p>
- *
- * <h4>Styling from a grammar:</h4>
- * Each scope name given in the grammar is converted to an array of CSS class names. For example
- * a region of text with scope <tt>keyword.control.php</tt> will be assigned the CSS classes
- * <pre>keyword, keyword-control, keyword-control-php</pre>
- *
- * A CSS file can give rules matching any of these class names to provide generic or more specific styling.
- * For example, <pre>.keyword { font-color: blue; }</pre> colors all keywords blue, while
- * <pre>.keyword-control-php { font-weight: bold; }</pre> bolds only PHP control keywords.
- *
- * This is useful when using grammars that adhere to TextMate's
- * <a href="http://manual.macromates.com/en/language_grammars.html#naming_conventions">scope name conventions</a>,
- * as a single CSS rule can provide consistent styling to similar constructs across different languages.<p>
- *
- * <h4>Supported top-level grammar features:</h4>
- * <ul><li><tt>fileTypes, patterns, repository</tt> (but see below) are supported.</li>
- * <li><tt>scopeName, firstLineMatch, foldingStartMarker, foldingStopMarker</tt> are <b>not</b> supported.</li>
- * </ul>
- *
- * <p>TODO update this section</p>
- * <del>
- * <h4>Supported grammar rule features:</h4>
- * <ul><li><tt>match</tt> patterns are supported.</li>
- * <li><tt>name</tt> scope is supported.</li>
- * <li><tt>captures</tt> is <b>not</b> supported. Any scopes given inside a <tt>captures</tt> object are not applied.</li>
- * <li><tt>begin/end</tt> patterns are <b>not</b> supported and are ignored, along with their subrules. Consequently,
- * matched constructs may <b>not</b> span multiple lines.</li>
- * <li><tt>contentName, beginCaptures, endCaptures, applyEndPatternLast</tt> are <b>not</b> supported.</li>
- * <li><tt>include</tt> is supported, but only when it references a rule in the current grammar's <tt>repository</tt>.
- * Including <tt>$self</tt>, <tt>$base</tt>, or <tt>rule.from.another.grammar</tt> is <b>not</b> supported.</li>
- * <li>The <tt>(?x)</tt> option ("extended" regex format) is supported, but only when it appears at the beginning of a regex pattern.</li>
- * <li>Matching is done using native JavaScript {@link RegExp}s. As a result, many Oniguruma features are <b>not</b> supported.
- * Unsupported features include:
- * <ul><li>Named captures</li>
- * <li>Setting flags inside groups (eg. <tt>(?i:a)b</tt>)</li>
- * <li>Lookbehind and negative lookbehind</li>
- * <li>Subexpression call</li>
- * <li>etc.</li>
- * </ul>
- * </li>
- * </ul>
- * </del>
- *
- * @class orion.editor.TextMateStyler
- * @extends orion.editor.AbstractStyler
- * @param {orion.textview.TextView} textView The textView.
- * @param {Object} grammar The TextMate grammar as a JavaScript object. You can use a plist-to-JavaScript conversion tool
- * to produce this object. Note that some features of TextMate grammars are not supported.
- */
- orion.editor.TextMateStyler = (function() {
- /** @inner */
- function TextMateStyler(textView, grammar) {
- this.initialize(textView);
- // Copy the grammar since we'll mutate it
- this.grammar = this.copy(grammar);
- this._styles = {}; /* key: {String} scopeName, value: {String[]} cssClassNames */
- this._tree = null;
-
- this.preprocess();
- }
- orion.editor.AbstractStyler.extend(TextMateStyler, /** @lends orion.editor.TextMateStyler.prototype */ {
- copy: function(obj) {
- return JSON.parse(JSON.stringify(obj));
- },
- preprocess: function() {
- var stack = [this.grammar];
- for (; stack.length !== 0; ) {
- var rule = stack.pop();
- if (rule._resolvedRule && rule._typedRule) {
- continue;
- }
- // console.debug("Process " + (rule.include || rule.name));
-
- // Look up include'd rule, create typed *Rule instance
- rule._resolvedRule = this._resolve(rule);
- rule._typedRule = this._createTypedRule(rule);
-
- // Convert the scope names to styles and cache them for later
- this.addStyles(rule.name);
- this.addStyles(rule.contentName);
- this.addStylesForCaptures(rule.captures);
- this.addStylesForCaptures(rule.beginCaptures);
- this.addStylesForCaptures(rule.endCaptures);
-
- if (rule._resolvedRule !== rule) {
- // Add include target
- stack.push(rule._resolvedRule);
- }
- if (rule.patterns) {
- // Add subrules
- for (var i=0; i < rule.patterns.length; i++) {
- stack.push(rule.patterns[i]);
- }
- }
- }
- },
-
- /**
- * Adds eclipse.Style objects for scope to our _styles cache.
- * @param {String} scope A scope name, like "constant.character.php".
- */
- addStyles: function(scope) {
- if (scope && !this._styles[scope]) {
- this._styles[scope] = dojo.map(scope.split("."),
- function(segment, i, segments) {
- return segments.slice(0, i+1).join("-");
- });
- // console.debug("add style for " + scope + " = [" + this._styles[scope].join(", ") + "]");
- }
- },
- addStylesForCaptures: function(/**Object*/ captures) {
- for (var prop in captures) {
- if (captures.hasOwnProperty(prop)) {
- var scope = captures[prop].name;
- this.addStyles(scope);
- }
- }
- },
- /**
- * A rule that contains subrules ("patterns" in TextMate parlance) but has no "begin" or "end".
- * Also handles top level of grammar.
- * @private
- */
- ContainerRule: (function() {
- function ContainerRule(/**Object*/ rule) {
- this.rule = rule;
- this.subrules = rule.patterns;
- }
- ContainerRule.prototype.valueOf = function() { return "aa"; };
- return ContainerRule;
- }()),
- /**
- * A rule that is delimited by "begin" and "end" matches, which may be separated by any number of
- * lines. This type of rule may contain subrules, which apply only inside the begin .. end region.
- * @private
- */
- BeginEndRule: (function() {
- function BeginEndRule(/**Object*/ rule) {
- this.rule = rule;
- // TODO: the TextMate blog claims that "end" is optional.
- this.beginRegex = orion.editor.RegexUtil.toRegExp(rule.begin);
- this.endRegex = orion.editor.RegexUtil.toRegExp(rule.end);
- this.subrules = rule.patterns || [];
-
- this.endRegexHasBackRef = orion.editor.RegexUtil.hasBackReference(this.endRegex);
-
- // Deal with non-0 captures
- var complexCaptures = orion.editor.RegexUtil.complexCaptures(rule.captures);
- var complexBeginEnd = orion.editor.RegexUtil.complexCaptures(rule.beginCaptures) || orion.editor.RegexUtil.complexCaptures(rule.endCaptures);
- this.isComplex = complexCaptures || complexBeginEnd;
- if (this.isComplex) {
- var bg = orion.editor.RegexUtil.groupify(this.beginRegex);
- this.beginRegex = bg[0];
- this.beginOld2New = bg[1];
- this.beginConsuming = bg[2];
-
- var eg = orion.editor.RegexUtil.groupify(this.endRegex, this.beginOld2New /*Update end's backrefs to begin's new group #s*/);
- this.endRegex = eg[0];
- this.endOld2New = eg[1];
- this.endConsuming = eg[2];
- }
- }
- BeginEndRule.prototype.valueOf = function() { return this.beginRegex; };
- return BeginEndRule;
- }()),
- /**
- * A rule with a "match" pattern.
- * @private
- */
- MatchRule: (function() {
- function MatchRule(/**Object*/ rule) {
- this.rule = rule;
- this.matchRegex = orion.editor.RegexUtil.toRegExp(rule.match);
- this.isComplex = orion.editor.RegexUtil.complexCaptures(rule.captures);
- if (this.isComplex) {
- var mg = orion.editor.RegexUtil.groupify(this.matchRegex);
- this.matchRegex = mg[0];
- this.matchOld2New = mg[1];
- this.matchConsuming = mg[2];
- }
- }
- MatchRule.prototype.valueOf = function() { return this.matchRegex; };
- return MatchRule;
- }()),
- /**
- * @param {Object} rule A rule from the grammar.
- * @returns {MatchRule|BeginEndRule|ContainerRule}
- */
- _createTypedRule: function(rule) {
- if (rule.match) {
- return new this.MatchRule(rule);
- } else if (rule.begin) {
- return new this.BeginEndRule(rule);
- } else {
- return new this.ContainerRule(rule);
- }
- },
- /**
- * Resolves a rule from the grammar (which may be an include) into the real rule that it points to.
- */
- _resolve: function(rule) {
- var resolved = rule;
- if (rule.include) {
- if (rule.begin || rule.end || rule.match) {
- throw new Error("Unexpected regex pattern in \"include\" rule " + rule.include);
- }
- var name = rule.include;
- if (name.charAt(0) === "#") {
- resolved = this.grammar.repository && this.grammar.repository[name.substring(1)];
- if (!resolved) { throw new Error("Couldn't find included rule " + name + " in grammar repository"); }
- } else if (name === "$self") {
- resolved = this.grammar;
- } else if (name === "$base") {
- // $base is only relevant when including rules from foreign grammars
- throw new Error("Include \"$base\" is not supported");
- } else {
- throw new Error("Include external rule \"" + name + "\" is not supported");
- }
- }
- return resolved;
- },
- ContainerNode: (function() {
- function ContainerNode(parent, rule) {
- this.parent = parent;
- this.rule = rule;
- this.children = [];
-
- this.start = null;
- this.end = null;
- }
- ContainerNode.prototype.addChild = function(child) {
- this.children.push(child);
- };
- ContainerNode.prototype.valueOf = function() {
- var r = this.rule;
- return "ContainerNode { " + (r.include || "") + " " + (r.name || "") + (r.comment || "") + "}";
- };
- return ContainerNode;
- }()),
- BeginEndNode: (function() {
- function BeginEndNode(parent, rule, beginMatch) {
- this.parent = parent;
- this.rule = rule;
- this.children = [];
-
- this.setStart(beginMatch);
- this.end = null; // will be set eventually during parsing (may be EOF)
- this.endMatch = null; // may remain null if we never match our "end" pattern
-
- // Build a new regex if the "end" regex has backrefs since they refer to matched groups of beginMatch
- if (rule.endRegexHasBackRef) {
- this.endRegexSubstituted = orion.editor.RegexUtil.getSubstitutedRegex(rule.endRegex, beginMatch);
- } else {
- this.endRegexSubstituted = null;
- }
- }
- BeginEndNode.prototype.addChild = function(child) {
- this.children.push(child);
- };
- /** @return {Number} This node's index in its parent's "children" list */
- BeginEndNode.prototype.getIndexInParent = function(node) {
- return this.parent ? this.parent.children.indexOf(this) : -1;
- };
- /** @param {RegExp.match} beginMatch */
- BeginEndNode.prototype.setStart = function(beginMatch) {
- this.start = beginMatch.index;
- this.beginMatch = beginMatch;
- };
- /** @param {RegExp.match|Number} endMatchOrLastChar */
- BeginEndNode.prototype.setEnd = function(endMatchOrLastChar) {
- if (endMatchOrLastChar && typeof(endMatchOrLastChar) === "object") {
- var endMatch = endMatchOrLastChar;
- this.endMatch = endMatch;
- this.end = endMatch.index + endMatch[0].length;
- } else {
- var lastChar = endMatchOrLastChar;
- this.endMatch = null;
- this.end = lastChar;
- }
- };
- BeginEndNode.prototype.shiftStart = function(amount) {
- this.start += amount;
- this.beginMatch.index += amount;
- };
- BeginEndNode.prototype.shiftEnd = function(amount) {
- this.end += amount;
- if (this.endMatch) { this.endMatch.index += amount; }
- };
- BeginEndNode.prototype.valueOf = function() {
- return "{" + this.rule.beginRegex + " range=" + this.start + ".." + this.end + "}";
- };
- return BeginEndNode;
- }()),
- /** Pushes rules onto stack so that rules[startFrom] is on top */
- push: function(/**Array*/ stack, /**Array*/ rules) {
- if (!rules) { return; }
- for (var i = rules.length; i > 0; ) {
- stack.push(rules[--i]);
- }
- },
- /** Execs regex on text, and returns the match object with its index offset by the given amount. */
- exec: function(/**RegExp*/ regex, /**String*/ text, /**Number*/ offset) {
- var match = regex.exec(text);
- if (match) { match.index += offset; }
- regex.lastIndex = 0; // Just in case
- return match;
- },
- /** @returns {Number} The position immediately following the match. */
- afterMatch: function(/**RegExp.match*/ match) {
- return match.index + match[0].length;
- },
- /** @returns {RegExp.match} If node is a BeginEndNode and its rule's "end" pattern matches the text. */
- getEndMatch: function(/**Node*/ node, /**String*/ text, /**Number*/ offset) {
- if (node instanceof this.BeginEndNode) {
- var rule = node.rule;
- var endRegex = node.endRegexSubstituted || rule.endRegex;
- if (!endRegex) { return null; }
- return this.exec(endRegex, text, offset);
- }
- return null;
- },
- /** Called once when file is first loaded to build the parse tree. Tree is updated incrementally thereafter as buffer is modified */
- initialParse: function() {
- var last = this.textView.getModel().getCharCount();
- // First time; make parse tree for whole buffer
- var root = new this.ContainerNode(null, this.grammar._typedRule);
- this._tree = root;
- this.parse(this._tree, false, 0);
- },
- _onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) {
- var addedCharCount = e.addedCharCount,
- addedLineCount = e.addedLineCount,
- removedCharCount = e.removedCharCount,
- removedLineCount = e.removedLineCount,
- start = e.start;
- if (!this._tree) {
- this.initialParse();
- } else {
- var model = this.textView.getModel();
- var charCount = model.getCharCount();
-
- // For rs, we must rewind to the line preceding the line 'start' is on. We can't rely on start's
- // line since it may've been changed in a way that would cause a new beginMatch at its lineStart.
- var rs = model.getLineEnd(model.getLineAtOffset(start) - 1); // may be < 0
- var fd = this.getFirstDamaged(rs, rs);
- rs = rs === -1 ? 0 : rs;
- var stoppedAt;
- if (fd) {
- // [rs, re] is the region we need to verify. If we find the structure of the tree
- // has changed in that area, then we may need to reparse the rest of the file.
- stoppedAt = this.parse(fd, true, rs, start, addedCharCount, removedCharCount);
- } else {
- // FIXME: fd == null ?
- stoppedAt = charCount;
- }
- this.textView.redrawRange(rs, stoppedAt);
- }
- },
- /** @returns {BeginEndNode|ContainerNode} The result of taking the first (smallest "start" value)
- * node overlapping [start,end] and drilling down to get its deepest damaged descendant (if any).
- */
- getFirstDamaged: function(start, end) {
- // If start === 0 we actually have to start from the root because there is no position
- // we can rely on. (First index is damaged)
- if (start < 0) {
- return this._tree;
- }
-
- var nodes = [this._tree];
- var result = null;
- while (nodes.length) {
- var n = nodes.pop();
- if (!n.parent /*n is root*/ || this.isDamaged(n, start, end)) {
- // n is damaged by the edit, so go into its children
- // Note: If a node is damaged, then some of its descendents MAY be damaged
- // If a node is undamaged, then ALL of its descendents are undamaged
- if (n instanceof this.BeginEndNode) {
- result = n;
- }
- // Examine children[0] last
- for (var i=0; i < n.children.length; i++) {
- nodes.push(n.children[i]);
- }
- }
- }
- return result || this._tree;
- },
- /** @returns true If n overlaps the interval [start,end] */
- isDamaged: function(/**BeginEndNode*/ n, start, end) {
- // Note strict > since [2,5] doesn't overlap [5,7]
- return (n.start <= end && n.end > start);
- },
- /**
- * Builds tree from some of the buffer content
- *
- * TODO cleanup params
- * @param {BeginEndNode|ContainerNode} origNode The deepest node that overlaps [rs,rs], or the root.
- * @param {Boolean} repairing
- * @param {Number} rs See _onModelChanged()
- * @param {Number} [editStart] Only used for repairing === true
- * @param {Number} [addedCharCount] Only used for repairing === true
- * @param {Number} [removedCharCount] Only used for repairing === true
- */
- parse: function(origNode, repairing, rs, editStart, addedCharCount, removedCharCount) {
- var model = this.textView.getModel();
- var lastLineStart = model.getLineStart(model.getLineCount() - 1);
- var eof = model.getCharCount();
- var initialExpected = this.getInitialExpected(origNode, rs);
-
- // re is best-case stopping point; if we detect change to tree, we must continue past it
- var re = -1;
- if (repairing) {
- origNode.repaired = true;
- origNode.endNeedsUpdate = true;
- var lastChild = origNode.children[origNode.children.length-1];
- var delta = addedCharCount - removedCharCount;
- var lastChildLineEnd = lastChild ? model.getLineEnd(model.getLineAtOffset(lastChild.end + delta)) : -1;
- var editLineEnd = model.getLineEnd(model.getLineAtOffset(editStart + removedCharCount));
- re = Math.max(lastChildLineEnd, editLineEnd);
- }
- re = (re === -1) ? eof : re;
-
- var expected = initialExpected;
- var node = origNode;
- var matchedChildOrEnd = false;
- var pos = rs;
- while (node && (!repairing || (pos < re))) {
- var matchInfo = this.getNextMatch(model, node, pos);
- if (!matchInfo) {
- // Go to next line, if any
- pos = (pos >= lastLineStart) ? eof : model.getLineStart(model.getLineAtOffset(pos) + 1);
- }
- var match = matchInfo && matchInfo.match,
- rule = matchInfo && matchInfo.rule,
- isSub = matchInfo && matchInfo.isSub,
- isEnd = matchInfo && matchInfo.isEnd;
- if (isSub) {
- pos = this.afterMatch(match);
- if (rule instanceof this.BeginEndRule) {
- matchedChildOrEnd = true;
- // Matched a child. Did we expect that?
- if (repairing && rule === expected.rule && node === expected.parent) {
- // Yes: matched expected child
- var foundChild = expected;
- foundChild.setStart(match);
- // Note: the 'end' position for this node will either be matched, or fixed up by us post-loop
- foundChild.repaired = true;
- foundChild.endNeedsUpdate = true;
- node = foundChild; // descend
- expected = this.getNextExpected(expected, "begin");
- } else {
- if (repairing) {
- // No: matched unexpected child.
- this.prune(node, expected);
- repairing = false;
- }
-
- // Add the new child (will replace 'expected' in node's children list)
- var subNode = new this.BeginEndNode(node, rule, match);
- node.addChild(subNode);
- node = subNode; // descend
- }
- } else {
- // Matched a MatchRule; no changes to tree required
- }
- } else if (isEnd || pos === eof) {
- if (node instanceof this.BeginEndNode) {
- if (match) {
- matchedChildOrEnd = true;
- node.setEnd(match);
- pos = this.afterMatch(match);
- // Matched node's end. Did we expect that?
- if (repairing && node === expected && node.parent === expected.parent) {
- // Yes: found the expected end of node
- node.repaired = true;
- delete node.endNeedsUpdate;
- expected = this.getNextExpected(expected, "end");
- } else {
- if (repairing) {
- // No: found an unexpected end
- this.prune(node, expected);
- repairing = false;
- }
- }
- } else {
- // Force-ending a BeginEndNode that runs until eof
- node.setEnd(eof);
- delete node.endNeedsUpdate;
- }
- }
- node = node.parent; // ascend
- }
-
- // if (repairing && pos >= re && !matchedChildOrEnd) {
- // // Reached re without matching any begin/end => initialExpected itself was removed => repair fail
- // this.prune(origNode, initialExpected);
- // repairing = false;
- // }
- } // end loop
- // TODO: do this for every node we end?
- this.removeUnrepairedChildren(origNode, repairing, rs);
-
- //console.debug("parsed " + (pos - rs) + " of " + model.getCharCount + "buf");
- this.cleanup(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount);
- return pos; // where we stopped repairing/reparsing
- },
- /** Helper for parse() in the repair case. To be called when ending a node, as any children that
- * lie in [rs,node.end] and were not repaired must've been deleted.
- */
- removeUnrepairedChildren: function(node, repairing, start) {
- if (repairing) {
- var children = node.children;
- var removeFrom = -1;
- for (var i=0; i < children.length; i++) {
- var child = children[i];
- if (!child.repaired && this.isDamaged(child, start, Number.MAX_VALUE /*end doesn't matter*/)) {
- removeFrom = i;
- break;
- }
- }
- if (removeFrom !== -1) {
- node.children.length = removeFrom;
- }
- }
- },
- /** Helper for parse() in the repair case */
- cleanup: function(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount) {
- var i, node, maybeRepairedNodes;
- if (repairing) {
- // The repair succeeded, so update stale begin/end indices by simple translation.
- var delta = addedCharCount - removedCharCount;
- // A repaired node's end can't exceed re, but it may exceed re-delta+1.
- // TODO: find a way to guarantee disjoint intervals for repaired vs unrepaired, then stop using flag
- var maybeUnrepairedNodes = this.getIntersecting(re-delta+1, eof);
- maybeRepairedNodes = this.getIntersecting(rs, re);
- // Handle unrepaired nodes. They are those intersecting [re-delta+1, eof] that don't have the flag
- for (i=0; i < maybeUnrepairedNodes.length; i++) {
- node = maybeUnrepairedNodes[i];
- if (!node.repaired && node instanceof this.BeginEndNode) {
- node.shiftEnd(delta);
- node.shiftStart(delta);
- }
- }
- // Translate 'end' index of repaired node whose 'end' was not matched in loop (>= re)
- for (i=0; i < maybeRepairedNodes.length; i++) {
- node = maybeRepairedNodes[i];
- if (node.repaired && node.endNeedsUpdate) {
- node.shiftEnd(delta);
- }
- delete node.endNeedsUpdate;
- delete node.repaired;
- }
- } else {
- // Clean up after ourself
- maybeRepairedNodes = this.getIntersecting(rs, re);
- for (i=0; i < maybeRepairedNodes.length; i++) {
- delete maybeRepairedNodes[i].repaired;
- }
- }
- },
- /**
- * @param model {orion.textview.TextModel}
- * @param node {Node}
- * @param pos {Number}
- * @param [matchRulesOnly] {Boolean} Optional, if true only "match" subrules will be considered.
- * @returns {Object} A match info object with properties:
- * {Boolean} isEnd
- * {Boolean} isSub
- * {RegExp.match} match
- * {(Match|BeginEnd)Rule} rule
- */
- getNextMatch: function(model, node, pos, matchRulesOnly) {
- var lineIndex = model.getLineAtOffset(pos);
- var lineEnd = model.getLineEnd(lineIndex);
- var line = model.getText(pos, lineEnd);
- var stack = [],
- expandedContainers = [],
- subMatches = [],
- subrules = [];
- this.push(stack, node.rule.subrules);
- while (stack.length) {
- var next = stack.length ? stack.pop() : null;
- var subrule = next && next._resolvedRule._typedRule;
- if (subrule instanceof this.ContainerRule && expandedContainers.indexOf(subrule) === -1) {
- // Expand ContainerRule by pushing its subrules on
- expandedContainers.push(subrule);
- this.push(stack, subrule.subrules);
- continue;
- }
- if (subrule && matchRulesOnly && !(subrule.matchRegex)) {
- continue;
- }
- var subMatch = subrule && this.exec(subrule.matchRegex || subrule.beginRegex, line, pos);
- if (subMatch) {
- subMatches.push(subMatch);
- subrules.push(subrule);
- }
- }
- var bestSub = Number.MAX_VALUE,
- bestSubIndex = -1;
- for (var i=0; i < subMatches.length; i++) {
- var match = subMatches[i];
- if (match.index < bestSub) {
- bestSub = match.index;
- bestSubIndex = i;
- }
- }
-
- if (!matchRulesOnly) {
- // See if the "end" pattern of the active begin/end node matches.
- // TODO: The active begin/end node may not be the same as the node that holds the subrules
- var activeBENode = node;
- var endMatch = this.getEndMatch(node, line, pos);
- if (endMatch) {
- var doEndLast = activeBENode.rule.applyEndPatternLast;
- var endWins = bestSubIndex === -1 || (endMatch.index < bestSub) || (!doEndLast && endMatch.index === bestSub);
- if (endWins) {
- return {isEnd: true, rule: activeBENode.rule, match: endMatch};
- }
- }
- }
- return bestSubIndex === -1 ? null : {isSub: true, rule: subrules[bestSubIndex], match: subMatches[bestSubIndex]};
- },
- /**
- * Gets the node corresponding to the first match we expect to see in the repair.
- * @param {BeginEndNode|ContainerNode} node The node returned via getFirstDamaged(rs,rs) -- may be the root.
- * @param {Number} rs See _onModelChanged()
- * Note that because rs is a line end (or 0, a line start), it will intersect a beginMatch or
- * endMatch either at their 0th character, or not at all. (begin/endMatches can't cross lines).
- * This is the only time we rely on the start/end values from the pre-change tree. After this
- * we only look at node ordering, never use the old indices.
- * @returns {Node}
- */
- getInitialExpected: function(node, rs) {
- // TODO: Kind of weird.. maybe ContainerNodes should have start & end set, like BeginEndNodes
- var i, child;
- if (node === this._tree) {
- // get whichever of our children comes after rs
- for (i=0; i < node.children.length; i++) {
- child = node.children[i]; // BeginEndNode
- if (child.start >= rs) {
- return child;
- }
- }
- } else if (node instanceof this.BeginEndNode) {
- if (node.endMatch) {
- // Which comes next after rs: our nodeEnd or one of our children?
- var nodeEnd = node.endMatch.index;
- for (i=0; i < node.children.length; i++) {
- child = node.children[i]; // BeginEndNode
- if (child.start >= rs) {
- break;
- }
- }
- if (child && child.start < nodeEnd) {
- return child; // Expect child as the next match
- }
- } else {
- // No endMatch => node goes until eof => it end should be the next match
- }
- }
- return node; // We expect node to end, so it should be the next match
- },
- /**
- * Helper for repair() to tell us what kind of event we expect next.
- * @param {Node} expected Last value returned by this method.
- * @param {String} event "begin" if the last value of expected was matched as "begin",
- * or "end" if it was matched as an end.
- * @returns {Node} The next expected node to match, or null.
- */
- getNextExpected: function(/**Node*/ expected, event) {
- var node = expected;
- if (event === "begin") {
- var child = node.children[0];
- if (child) {
- return child;
- } else {
- return node;
- }
- } else if (event === "end") {
- var parent = node.parent;
- if (parent) {
- var nextSibling = parent.children[parent.children.indexOf(node) + 1];
- if (nextSibling) {
- return nextSibling;
- } else {
- return parent;
- }
- }
- }
- return null;
- },
- /** Helper for parse() when repairing. Prunes out the unmatched nodes from the tree so we can continue parsing. */
- prune: function(/**BeginEndNode|ContainerNode*/ node, /**Node*/ expected) {
- var expectedAChild = expected.parent === node;
- if (expectedAChild) {
- // Expected child wasn't matched; prune it and all siblings after it
- node.children.length = expected.getIndexInParent();
- } else if (node instanceof this.BeginEndNode) {
- // Expected node to end but it didn't; set its end unknown and we'll match it eventually
- node.endMatch = null;
- node.end = null;
- }
- // Reparsing from node, so prune the successors outside of node's subtree
- if (node.parent) {
- node.parent.children.length = node.getIndexInParent() + 1;
- }
- },
- _onLineStyle: function(/**eclipse.LineStyleEvent*/ e) {
- function byStart(r1, r2) {
- return r1.start - r2.start;
- }
-
- if (!this._tree) {
- // In some cases it seems onLineStyle is called before onModelChanged, so we need to parse here
- this.initialParse();
- }
- var lineStart = e.lineStart,
- model = this.textView.getModel(),
- lineEnd = model.getLineEnd(e.lineIndex);
-
- var rs = model.getLineEnd(model.getLineAtOffset(lineStart) - 1); // may be < 0
- var node = this.getFirstDamaged(rs, rs);
-
- var scopes = this.getLineScope(model, node, lineStart, lineEnd);
- e.ranges = this.toStyleRanges(scopes);
- // // Editor requires StyleRanges must be in ascending order by 'start', or else some will be ignored
- e.ranges.sort(byStart);
- },
- // Run parse algorithm on [start, end] in the context of node, assigning scope as we find matches
- getLineScope: function(model, node, start, end) {
- var pos = start;
- var expected = this.getInitialExpected(node, start);
- var scopes = [],
- gaps = [];
- while (node && (pos < end)) {
- var matchInfo = this.getNextMatch(model, node, pos);
- if (!matchInfo) {
- break; // line is over
- }
- var match = matchInfo && matchInfo.match,
- rule = matchInfo && matchInfo.rule,
- isSub = matchInfo && matchInfo.isSub,
- isEnd = matchInfo && matchInfo.isEnd;
- if (match.index !== pos) {
- // gap [pos..match.index]
- gaps.push({ start: pos, end: match.index, node: node});
- }
- if (isSub) {
- pos = this.afterMatch(match);
- if (rule instanceof this.BeginEndRule) {
- // Matched a "begin", assign its scope and descend into it
- this.addBeginScope(scopes, match, rule);
- node = expected; // descend
- expected = this.getNextExpected(expected, "begin");
- } else {
- // Matched a child MatchRule;
- this.addMatchScope(scopes, match, rule);
- }
- } else if (isEnd) {
- pos = this.afterMatch(match);
- // Matched and "end", assign its end scope and go up
- this.addEndScope(scopes, match, rule);
- expected = this.getNextExpected(expected, "end");
- node = node.parent; // ascend
- }
- }
- if (pos < end) {
- gaps.push({ start: pos, end: end, node: node });
- }
- var inherited = this.getInheritedLineScope(gaps, start, end);
- return scopes.concat(inherited);
- },
- getInheritedLineScope: function(gaps, start, end) {
- var scopes = [];
- for (var i=0; i < gaps.length; i++) {
- var gap = gaps[i];
- var node = gap.node;
- while (node) {
- // if node defines a contentName or name, apply it
- var rule = node.rule.rule;
- var name = rule.name,
- contentName = rule.contentName;
- // TODO: if both are given, we don't resolve the conflict. contentName always wins
- var scope = contentName || name;
- if (scope) {
- this.addScopeRange(scopes, gap.start, gap.end, scope);
- break;
- }
- node = node.parent;
- }
- }
- return scopes;
- },
- addBeginScope: function(scopes, match, typedRule) {
- var rule = typedRule.rule;
- this.addCapturesScope(scopes, match, (rule.beginCaptures || rule.captures), typedRule.isComplex, typedRule.beginOld2New, typedRule.beginConsuming);
- },
- addEndScope: function(scopes, match, typedRule) {
- var rule = typedRule.rule;
- this.addCapturesScope(scopes, match, (rule.endCaptures || rule.captures), typedRule.isComplex, typedRule.endOld2New, typedRule.endConsuming);
- },
- addMatchScope: function(scopes, match, typedRule) {
- var rule = typedRule.rule,
- name = rule.name,
- captures = rule.captures;
- if (captures) {
- // captures takes priority over name
- this.addCapturesScope(scopes, match, captures, typedRule.isComplex, typedRule.matchOld2New, typedRule.matchConsuming);
- } else {
- this.addScope(scopes, match, name);
- }
- },
- addScope: function(scopes, match, name) {
- if (!name) { return; }
- scopes.push({start: match.index, end: this.afterMatch(match), scope: name });
- },
- addScopeRange: function(scopes, start, end, name) {
- if (!name) { return; }
- scopes.push({start: start, end: end, scope: name });
- },
- addCapturesScope: function(/**Array*/scopes, /*RegExp.match*/ match, /**Object*/captures, /**Boolean*/isComplex, /**Object*/old2New, /**Object*/consuming) {
- if (!captures) { return; }
- if (!isComplex) {
- this.addScope(scopes, match, captures[0] && captures[0].name);
- } else {
- // apply scopes captures[1..n] to matching groups [1]..[n] of match
-
- // Sum up the lengths of preceding consuming groups to get the start offset for each matched group.
- var newGroupStarts = {1: 0};
- var sum = 0;
- for (var num = 1; match[num] !== undefined; num++) {
- if (consuming[num] !== undefined) {
- sum += match[num].length;
- }
- if (match[num+1] !== undefined) {
- newGroupStarts[num + 1] = sum;
- }
- }
- // Map the group numbers referred to in captures object to the new group numbers, and get the actual matched range.
- var start = match.index;
- for (var oldGroupNum = 1; captures[oldGroupNum]; oldGroupNum++) {
- var scope = captures[oldGroupNum].name;
- var newGroupNum = old2New[oldGroupNum];
- var groupStart = start + newGroupStarts[newGroupNum];
- // Not every capturing group defined in regex need match every time the regex is run.
- // eg. (a)|b matches "b" but group 1 is undefined
- if (typeof match[newGroupNum] !== "undefined") {
- var groupEnd = groupStart + match[newGroupNum].length;
- this.addScopeRange(scopes, groupStart, groupEnd, scope);
- }
- }
- }
- },
- /** @returns {Node[]} In depth-first order */
- getIntersecting: function(start, end) {
- var result = [];
- var nodes = this._tree ? [this._tree] : [];
- while (nodes.length) {
- var n = nodes.pop();
- var visitChildren = false;
- if (n instanceof this.ContainerNode) {
- visitChildren = true;
- } else if (this.isDamaged(n, start, end)) {
- visitChildren = true;
- result.push(n);
- }
- if (visitChildren) {
- var len = n.children.length;
- // for (var i=len-1; i >= 0; i--) {
- // nodes.push(n.children[i]);
- // }
- for (var i=0; i < len; i++) {
- nodes.push(n.children[i]);
- }
- }
- }
- return result.reverse();
- },
- _onSelection: function(e) {
- },
- _onDestroy: function(/**eclipse.DestroyEvent*/ e) {
- this.grammar = null;
- this._styles = null;
- this._tree = null;
- },
- /**
- * Applies the grammar to obtain the {@link eclipse.StyleRange[]} for the given line.
- * @returns eclipse.StyleRange[]
- */
- toStyleRanges: function(/**ScopeRange[]*/ scopeRanges) {
- var styleRanges = [];
- for (var i=0; i < scopeRanges.length; i++) {
- var scopeRange = scopeRanges[i];
- var classNames = this._styles[scopeRange.scope];
- if (!classNames) { throw new Error("styles not found for " + scopeRange.scope); }
- var classNamesString = classNames.join(" ");
- styleRanges.push({start: scopeRange.start, end: scopeRange.end, style: {styleClass: classNamesString}});
- // console.debug("{start " + styleRanges[i].start + ", end " + styleRanges[i].end + ", style: " + styleRanges[i].style.styleClass + "}");
- }
- return styleRanges;
- }
- });
- return TextMateStyler;
- }());
- if (typeof window !== "undefined" && typeof window.define !== "undefined") {
- define(['dojo'], function() {
- return orion.editor;
- });
- }