compact_bool: false,
// Use self-closing style for writing empty elements, e.g.
or
self_closing_tag: 'xhtml',
// Profile-level output filters, re-defines syntax filters
filters: '',
// Additional filters applied to abbreviation.
// Unlike "filters", this preference doesn't override default filters
// but add the instead every time given profile is chosen
extraFilters: ''
};
/**
* @constructor
* @type OutputProfile
* @param {Object} options
*/
function OutputProfile(options) {
utils.extend(this, defaultProfile, options);
}
OutputProfile.prototype = {
/**
* Transforms tag name case depending on current profile settings
* @param {String} name String to transform
* @returns {String}
*/
tagName: function(name) {
return stringCase(name, this.tag_case);
},
/**
* Transforms attribute name case depending on current profile settings
* @param {String} name String to transform
* @returns {String}
*/
attributeName: function(name) {
return stringCase(name, this.attr_case);
},
/**
* Returns quote character for current profile
* @returns {String}
*/
attributeQuote: function() {
return this.attr_quotes == 'single' ? "'" : '"';
},
/**
* Returns self-closing tag symbol for current profile
* @returns {String}
*/
selfClosing: function() {
if (this.self_closing_tag == 'xhtml')
return ' /';
if (this.self_closing_tag === true)
return '/';
return '';
},
/**
* Returns cursor token based on current profile settings
* @returns {String}
*/
cursor: function() {
return this.place_cursor ? utils.getCaretPlaceholder() : '';
},
/**
* Check if attribute with given name is boolean,
* e.g. written as `contenteditable` instead of
* `contenteditable="contenteditable"`
* @param {String} name Attribute name
* @return {Boolean}
*/
isBoolean: function(name, value) {
if (name == value) {
return true;
}
var boolAttrs = prefs.get('profile.booleanAttributes');
if (!value && boolAttrs) {
boolAttrs = new RegExp(boolAttrs, 'i');
return boolAttrs.test(name);
}
return false;
},
/**
* Check if compact boolean attribute record is
* allowed for current profile
* @return {Boolean}
*/
allowCompactBoolean: function() {
return this.compact_bool && prefs.get('profile.allowCompactBoolean');
}
};
/**
* Helper function that converts string case depending on
*
caseValue
* @param {String} str String to transform
* @param {String} caseValue Case value: can be
lower,
*
upper and
leave
* @returns {String}
*/
function stringCase(str, caseValue) {
switch (String(caseValue || '').toLowerCase()) {
case 'lower':
return str.toLowerCase();
case 'upper':
return str.toUpperCase();
}
return str;
}
/**
* Creates new output profile
* @param {String} name Profile name
* @param {Object} options Profile options
*/
function createProfile(name, options) {
return profiles[name.toLowerCase()] = new OutputProfile(options);
}
function createDefaultProfiles() {
createProfile('xhtml');
createProfile('html', {self_closing_tag: false, compact_bool: true});
createProfile('xml', {self_closing_tag: true, tag_nl: true});
createProfile('plain', {tag_nl: false, indent: false, place_cursor: false});
createProfile('line', {tag_nl: false, indent: false, extraFilters: 's'});
createProfile('css', {tag_nl: true});
createProfile('css_line', {tag_nl: false});
}
createDefaultProfiles();
return {
/**
* Creates new output profile and adds it into internal dictionary
* @param {String} name Profile name
* @param {Object} options Profile options
* @memberOf emmet.profile
* @returns {Object} New profile
*/
create: function(name, options) {
if (arguments.length == 2)
return createProfile(name, options);
else
// create profile object only
return new OutputProfile(utils.defaults(name || {}, defaultProfile));
},
/**
* Returns profile by its name. If profile wasn't found, returns
* 'plain' profile
* @param {String} name Profile name. Might be profile itself
* @param {String} syntax. Optional. Current editor syntax. If defined,
* profile is searched in resources first, then in predefined profiles
* @returns {Object}
*/
get: function(name, syntax) {
if (!name && syntax) {
// search in user resources first
var profile = resources.findItem(syntax, 'profile');
if (profile) {
name = profile;
}
}
if (!name) {
return profiles.plain;
}
if (name instanceof OutputProfile) {
return name;
}
if (typeof name === 'string' && name.toLowerCase() in profiles) {
return profiles[name.toLowerCase()];
}
return this.create(name);
},
/**
* Deletes profile with specified name
* @param {String} name Profile name
*/
remove: function(name) {
name = (name || '').toLowerCase();
if (name in profiles)
delete profiles[name];
},
/**
* Resets all user-defined profiles
*/
reset: function() {
profiles = {};
createDefaultProfiles();
},
/**
* Helper function that converts string case depending on
*
caseValue
* @param {String} str String to transform
* @param {String} caseValue Case value: can be
lower,
*
upper and
leave
* @returns {String}
*/
stringCase: stringCase
};
});
},{"../utils/common":"utils/common.js","./preferences":"assets/preferences.js","./resources":"assets/resources.js"}],"assets/range.js":[function(require,module,exports){
/**
* Helper module to work with ranges
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
function cmp(a, b, op) {
switch (op) {
case 'eq':
case '==':
return a === b;
case 'lt':
case '<':
return a < b;
case 'lte':
case '<=':
return a <= b;
case 'gt':
case '>':
return a > b;
case 'gte':
case '>=':
return a >= b;
}
}
/**
* @type Range
* @constructor
* @param {Object} start
* @param {Number} len
*/
function Range(start, len) {
if (typeof start === 'object' && 'start' in start) {
// create range from object stub
this.start = Math.min(start.start, start.end);
this.end = Math.max(start.start, start.end);
} else if (Array.isArray(start)) {
this.start = start[0];
this.end = start[1];
} else {
len = typeof len === 'string' ? len.length : +len;
this.start = start;
this.end = start + len;
}
}
Range.prototype = {
length: function() {
return Math.abs(this.end - this.start);
},
/**
* Returns
true
if passed range is equals to current one
* @param {Range} range
* @returns {Boolean}
*/
equal: function(range) {
return this.cmp(range, 'eq', 'eq');
// return this.start === range.start && this.end === range.end;
},
/**
* Shifts indexes position with passed
delta
* @param {Number} delta
* @returns {Range} range itself
*/
shift: function(delta) {
this.start += delta;
this.end += delta;
return this;
},
/**
* Check if two ranges are overlapped
* @param {Range} range
* @returns {Boolean}
*/
overlap: function(range) {
return range.start <= this.end && range.end >= this.start;
},
/**
* Finds intersection of two ranges
* @param {Range} range
* @returns {Range}
null
if ranges does not overlap
*/
intersection: function(range) {
if (this.overlap(range)) {
var start = Math.max(range.start, this.start);
var end = Math.min(range.end, this.end);
return new Range(start, end - start);
}
return null;
},
/**
* Returns the union of the thow ranges.
* @param {Range} range
* @returns {Range}
null
if ranges are not overlapped
*/
union: function(range) {
if (this.overlap(range)) {
var start = Math.min(range.start, this.start);
var end = Math.max(range.end, this.end);
return new Range(start, end - start);
}
return null;
},
/**
* Returns a Boolean value that indicates whether a specified position
* is in a given range.
* @param {Number} loc
*/
inside: function(loc) {
return this.cmp(loc, 'lte', 'gt');
// return this.start <= loc && this.end > loc;
},
/**
* Returns a Boolean value that indicates whether a specified position
* is in a given range, but not equals bounds.
* @param {Number} loc
*/
contains: function(loc) {
return this.cmp(loc, 'lt', 'gt');
},
/**
* Check if current range completely includes specified one
* @param {Range} r
* @returns {Boolean}
*/
include: function(r) {
return this.cmp(r, 'lte', 'gte');
// return this.start <= r.start && this.end >= r.end;
},
/**
* Low-level comparision method
* @param {Number} loc
* @param {String} left Left comparison operator
* @param {String} right Right comaprison operator
*/
cmp: function(loc, left, right) {
var a, b;
if (loc instanceof Range) {
a = loc.start;
b = loc.end;
} else {
a = b = loc;
}
return cmp(this.start, a, left || '<=') && cmp(this.end, b, right || '>');
},
/**
* Returns substring of specified
str
for current range
* @param {String} str
* @returns {String}
*/
substring: function(str) {
return this.length() > 0
? str.substring(this.start, this.end)
: '';
},
/**
* Creates copy of current range
* @returns {Range}
*/
clone: function() {
return new Range(this.start, this.length());
},
/**
* @returns {Array}
*/
toArray: function() {
return [this.start, this.end];
},
toString: function() {
return this.valueOf();
},
valueOf: function() {
return '{' + this.start + ', ' + this.length() + '}';
}
};
/**
* Creates new range object instance
* @param {Object} start Range start or array with 'start' and 'end'
* as two first indexes or object with 'start' and 'end' properties
* @param {Number} len Range length or string to produce range from
* @returns {Range}
*/
module.exports = function(start, len) {
if (typeof start == 'undefined' || start === null)
return null;
if (start instanceof Range)
return start;
if (typeof start == 'object' && 'start' in start && 'end' in start) {
len = start.end - start.start;
start = start.start;
}
return new Range(start, len);
};
module.exports.create = module.exports;
module.exports.isRange = function(val) {
return val instanceof Range;
};
/**
*
Range
object factory, the same as
this.create()
* but last argument represents end of range, not length
* @returns {Range}
*/
module.exports.create2 = function(start, end) {
if (typeof start === 'number' && typeof end === 'number') {
end -= start;
}
return this.create(start, end);
};
/**
* Helper function that sorts ranges in order as they
* appear in text
* @param {Array} ranges
* @return {Array}
*/
module.exports.sort = function(ranges, reverse) {
ranges = ranges.sort(function(a, b) {
if (a.start === b.start) {
return b.end - a.end;
}
return a.start - b.start;
});
reverse && ranges.reverse();
return ranges;
};
return module.exports;
});
},{}],"assets/resources.js":[function(require,module,exports){
/**
* Parsed resources (snippets, abbreviations, variables, etc.) for Emmet.
* Contains convenient method to get access for snippets with respect of
* inheritance. Also provides ability to store data in different vocabularies
* ('system' and 'user') for fast and safe resource update
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var handlerList = require('./handlerList');
var utils = require('../utils/common');
var elements = require('./elements');
var logger = require('../assets/logger');
var stringScore = require('../vendor/stringScore');
var cssResolver = require('../resolver/css');
var VOC_SYSTEM = 'system';
var VOC_USER = 'user';
var cache = {};
/** Regular expression for XML tag matching */
var reTag = /^<(\w+\:?[\w\-]*)((?:\s+[@\!]?[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/;
var systemSettings = {};
var userSettings = {};
/** @type HandlerList List of registered abbreviation resolvers */
var resolvers = handlerList.create();
function each(obj, fn) {
if (!obj) {
return;
}
Object.keys(obj).forEach(function(key) {
fn(obj[key], key);
});
}
/**
* Normalizes caret plceholder in passed text: replaces | character with
* default caret placeholder
* @param {String} text
* @returns {String}
*/
function normalizeCaretPlaceholder(text) {
return utils.replaceUnescapedSymbol(text, '|', utils.getCaretPlaceholder());
}
function parseItem(name, value, type) {
value = normalizeCaretPlaceholder(value);
if (type == 'snippets') {
return elements.create('snippet', value);
}
if (type == 'abbreviations') {
return parseAbbreviation(name, value);
}
}
/**
* Parses single abbreviation
* @param {String} key Abbreviation name
* @param {String} value Abbreviation value
* @return {Object}
*/
function parseAbbreviation(key, value) {
key = utils.trim(key);
var m;
if ((m = reTag.exec(value))) {
return elements.create('element', m[1], m[2], m[4] == '/');
} else {
// assume it's reference to another abbreviation
return elements.create('reference', value);
}
}
/**
* Normalizes snippet key name for better fuzzy search
* @param {String} str
* @returns {String}
*/
function normalizeName(str) {
return str.replace(/:$/, '').replace(/:/g, '-');
}
function expandSnippetsDefinition(snippets) {
var out = {};
each(snippets, function(val, key) {
var items = key.split('|');
// do not use iterators for better performance
for (var i = items.length - 1; i >= 0; i--) {
out[items[i]] = val;
}
});
return out;
}
utils.extend(exports, {
/**
* Sets new unparsed data for specified settings vocabulary
* @param {Object} data
* @param {String} type Vocabulary type ('system' or 'user')
* @memberOf resources
*/
setVocabulary: function(data, type) {
cache = {};
// sections like "snippets" and "abbreviations" could have
// definitions like `"f|fs": "fieldset"` which is the same as distinct
// "f" and "fs" keys both equals to "fieldset".
// We should parse these definitions first
var voc = {};
each(data, function(section, syntax) {
var _section = {};
each(section, function(subsection, name) {
if (name == 'abbreviations' || name == 'snippets') {
subsection = expandSnippetsDefinition(subsection);
}
_section[name] = subsection;
});
voc[syntax] = _section;
});
if (type == VOC_SYSTEM) {
systemSettings = voc;
} else {
userSettings = voc;
}
},
/**
* Returns resource vocabulary by its name
* @param {String} name Vocabulary name ('system' or 'user')
* @return {Object}
*/
getVocabulary: function(name) {
return name == VOC_SYSTEM ? systemSettings : userSettings;
},
/**
* Returns resource (abbreviation, snippet, etc.) matched for passed
* abbreviation
* @param {AbbreviationNode} node
* @param {String} syntax
* @returns {Object}
*/
getMatchedResource: function(node, syntax) {
return resolvers.exec(null, utils.toArray(arguments))
|| this.findSnippet(syntax, node.name());
},
/**
* Returns variable value
* @return {String}
*/
getVariable: function(name) {
return (this.getSection('variables') || {})[name];
},
/**
* Store runtime variable in user storage
* @param {String} name Variable name
* @param {String} value Variable value
*/
setVariable: function(name, value){
var voc = this.getVocabulary('user') || {};
if (!('variables' in voc))
voc.variables = {};
voc.variables[name] = value;
this.setVocabulary(voc, 'user');
},
/**
* Check if there are resources for specified syntax
* @param {String} syntax
* @return {Boolean}
*/
hasSyntax: function(syntax) {
return syntax in this.getVocabulary(VOC_USER)
|| syntax in this.getVocabulary(VOC_SYSTEM);
},
/**
* Registers new abbreviation resolver.
* @param {Function} fn Abbreviation resolver which will receive
* abbreviation as first argument and should return parsed abbreviation
* object if abbreviation has handled successfully,
null
* otherwise
* @param {Object} options Options list as described in
* {@link HandlerList#add()} method
*/
addResolver: function(fn, options) {
resolvers.add(fn, options);
},
removeResolver: function(fn) {
resolvers.remove(fn);
},
/**
* Returns actual section data, merged from both
* system and user data
* @param {String} name Section name (syntax)
* @param {String} ...args Subsections
* @returns
*/
getSection: function(name) {
if (!name)
return null;
if (!(name in cache)) {
cache[name] = utils.deepMerge({}, systemSettings[name], userSettings[name]);
}
var data = cache[name], subsections = utils.toArray(arguments, 1), key;
while (data && (key = subsections.shift())) {
if (key in data) {
data = data[key];
} else {
return null;
}
}
return data;
},
/**
* Recursively searches for a item inside top level sections (syntaxes)
* with respect of `extends` attribute
* @param {String} topSection Top section name (syntax)
* @param {String} subsection Inner section name
* @returns {Object}
*/
findItem: function(topSection, subsection) {
var data = this.getSection(topSection);
while (data) {
if (subsection in data)
return data[subsection];
data = this.getSection(data['extends']);
}
},
/**
* Recursively searches for a snippet definition inside syntax section.
* Definition is searched inside `snippets` and `abbreviations`
* subsections
* @param {String} syntax Top-level section name (syntax)
* @param {String} name Snippet name
* @returns {Object}
*/
findSnippet: function(syntax, name, memo) {
if (!syntax || !name)
return null;
memo = memo || [];
var names = [name];
// create automatic aliases to properties with colons,
// e.g. pos-a == pos:a
if (~name.indexOf('-')) {
names.push(name.replace(/\-/g, ':'));
}
var data = this.getSection(syntax), matchedItem = null;
['snippets', 'abbreviations'].some(function(sectionName) {
var data = this.getSection(syntax, sectionName);
if (data) {
return names.some(function(n) {
if (data[n]) {
return matchedItem = parseItem(n, data[n], sectionName);
}
});
}
}, this);
memo.push(syntax);
if (!matchedItem && data['extends'] && !~memo.indexOf(data['extends'])) {
// try to find item in parent syntax section
return this.findSnippet(data['extends'], name, memo);
}
return matchedItem;
},
/**
* Performs fuzzy search of snippet definition
* @param {String} syntax Top-level section name (syntax)
* @param {String} name Snippet name
* @returns
*/
fuzzyFindSnippet: function(syntax, name, minScore) {
var result = this.fuzzyFindMatches(syntax, name, minScore)[0];
if (result) {
return result.value.parsedValue;
}
},
fuzzyFindMatches: function(syntax, name, minScore) {
minScore = minScore || 0.3;
name = normalizeName(name);
var snippets = this.getAllSnippets(syntax);
return Object.keys(snippets)
.map(function(key) {
var value = snippets[key];
return {
key: key,
score: stringScore.score(value.nk, name, 0.1),
value: value
};
})
.filter(function(item) {
return item.score >= minScore;
})
.sort(function(a, b) {
return a.score - b.score;
})
.reverse();
},
/**
* Returns plain dictionary of all available abbreviations and snippets
* for specified syntax with respect of inheritance
* @param {String} syntax
* @returns {Object}
*/
getAllSnippets: function(syntax) {
var cacheKey = 'all-' + syntax;
if (!cache[cacheKey]) {
var stack = [], sectionKey = syntax;
var memo = [];
do {
var section = this.getSection(sectionKey);
if (!section)
break;
['snippets', 'abbreviations'].forEach(function(sectionName) {
var stackItem = {};
each(section[sectionName] || null, function(v, k) {
stackItem[k] = {
nk: normalizeName(k),
value: v,
parsedValue: parseItem(k, v, sectionName),
type: sectionName
};
});
stack.push(stackItem);
});
memo.push(sectionKey);
sectionKey = section['extends'];
} while (sectionKey && !~memo.indexOf(sectionKey));
cache[cacheKey] = utils.extend.apply(utils, stack.reverse());
}
return cache[cacheKey];
},
/**
* Returns newline character
* @returns {String}
*/
getNewline: function() {
var nl = this.getVariable('newline');
return typeof nl === 'string' ? nl : '\n';
},
/**
* Sets new newline character that will be used in output
* @param {String} str
*/
setNewline: function(str) {
this.setVariable('newline', str);
this.setVariable('nl', str);
}
});
// XXX add default resolvers
exports.addResolver(cssResolver.resolve.bind(cssResolver));
// try to load snippets
// hide it from Require.JS parser
(function(r) {
if (typeof define === 'undefined' || !define.amd) {
try {
var fs = r('fs');
var path = r('path');
var defaultSnippets = fs.readFileSync(path.join(__dirname, '../snippets.json'), {encoding: 'utf8'});
exports.setVocabulary(JSON.parse(defaultSnippets), VOC_SYSTEM);
} catch (e) {}
}
})(require);
return exports;
});
},{"../assets/logger":"assets/logger.js","../resolver/css":"resolver/css.js","../utils/common":"utils/common.js","../vendor/stringScore":"vendor/stringScore.js","./elements":"assets/elements.js","./handlerList":"assets/handlerList.js"}],"assets/stringStream.js":[function(require,module,exports){
/**
* A trimmed version of CodeMirror's StringStream module for string parsing
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
/**
* @type StringStream
* @constructor
* @param {String} string Assuming that bound string should be
* immutable
*/
function StringStream(string) {
this.pos = this.start = 0;
this.string = string;
this._length = string.length;
}
StringStream.prototype = {
/**
* Returns true only if the stream is at the end of the line.
* @returns {Boolean}
*/
eol: function() {
return this.pos >= this._length;
},
/**
* Returns true only if the stream is at the start of the line
* @returns {Boolean}
*/
sol: function() {
return this.pos === 0;
},
/**
* Returns the next character in the stream without advancing it.
* Will return
undefined
at the end of the line.
* @returns {String}
*/
peek: function() {
return this.string.charAt(this.pos);
},
/**
* Returns the next character in the stream and advances it.
* Also returns
undefined
when no more characters are available.
* @returns {String}
*/
next: function() {
if (this.pos < this._length)
return this.string.charAt(this.pos++);
},
/**
* match can be a character, a regular expression, or a function that
* takes a character and returns a boolean. If the next character in the
* stream 'matches' the given argument, it is consumed and returned.
* Otherwise, undefined is returned.
* @param {Object} match
* @returns {String}
*/
eat: function(match) {
var ch = this.string.charAt(this.pos), ok;
if (typeof match == "string")
ok = ch == match;
else
ok = ch && (match.test ? match.test(ch) : match(ch));
if (ok) {
++this.pos;
return ch;
}
},
/**
* Repeatedly calls
eat
with the given argument, until it
* fails. Returns
true
if any characters were eaten.
* @param {Object} match
* @returns {Boolean}
*/
eatWhile: function(match) {
var start = this.pos;
while (this.eat(match)) {}
return this.pos > start;
},
/**
* Shortcut for
eatWhile
when matching white-space.
* @returns {Boolean}
*/
eatSpace: function() {
var start = this.pos;
while (/[\s\u00a0]/.test(this.string.charAt(this.pos)))
++this.pos;
return this.pos > start;
},
/**
* Moves the position to the end of the line.
*/
skipToEnd: function() {
this.pos = this._length;
},
/**
* Skips to the next occurrence of the given character, if found on the
* current line (doesn't advance the stream if the character does not
* occur on the line). Returns true if the character was found.
* @param {String} ch
* @returns {Boolean}
*/
skipTo: function(ch) {
var found = this.string.indexOf(ch, this.pos);
if (found > -1) {
this.pos = found;
return true;
}
},
/**
* Skips to
close
character which is pair to
open
* character, considering possible pair nesting. This function is used
* to consume pair of characters, like opening and closing braces
* @param {String} open
* @param {String} close
* @returns {Boolean} Returns
true
if pair was successfully
* consumed
*/
skipToPair: function(open, close, skipString) {
var braceCount = 0, ch;
var pos = this.pos, len = this._length;
while (pos < len) {
ch = this.string.charAt(pos++);
if (ch == open) {
braceCount++;
} else if (ch == close) {
braceCount--;
if (braceCount < 1) {
this.pos = pos;
return true;
}
} else if (skipString && (ch == '"' || ch == "'")) {
this.skipString(ch);
}
}
return false;
},
/**
* A helper function which, in case of either single or
* double quote was found in current position, skips entire
* string (quoted value)
* @return {Boolean} Wether quoted string was skipped
*/
skipQuoted: function(noBackup) {
var ch = this.string.charAt(noBackup ? this.pos : this.pos - 1);
if (ch === '"' || ch === "'") {
if (noBackup) {
this.pos++;
}
return this.skipString(ch);
}
},
/**
* A custom function to skip string literal, e.g. a "double-quoted"
* or 'single-quoted' value
* @param {String} quote An opening quote
* @return {Boolean}
*/
skipString: function(quote) {
var pos = this.pos, len = this._length, ch;
while (pos < len) {
ch = this.string.charAt(pos++);
if (ch == '\\') {
continue;
} else if (ch == quote) {
this.pos = pos;
return true;
}
}
return false;
},
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
* @param {Number} n
*/
backUp : function(n) {
this.pos -= n;
},
/**
* Act like a multi-character
eat
—if
consume
is true or
* not given—or a look-ahead that doesn't update the stream position—if
* it is false.
pattern
can be either a string or a
* regular expression starting with ^. When it is a string,
*
caseInsensitive
can be set to true to make the match
* case-insensitive. When successfully matching a regular expression,
* the returned value will be the array returned by
match
,
* in case you need to extract matched groups.
*
* @param {RegExp} pattern
* @param {Boolean} consume
* @param {Boolean} caseInsensitive
* @returns
*/
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
var cased = caseInsensitive
? function(str) {return str.toLowerCase();}
: function(str) {return str;};
if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
if (consume !== false)
this.pos += pattern.length;
return true;
}
} else {
var match = this.string.slice(this.pos).match(pattern);
if (match && consume !== false)
this.pos += match[0].length;
return match;
}
},
/**
* Get the string between the start of the current token and the
* current stream position.
* @returns {String}
*/
current: function(backUp) {
return this.string.slice(this.start, this.pos - (backUp ? 1 : 0));
}
};
module.exports = function(string) {
return new StringStream(string);
};
/** @deprecated */
module.exports.create = module.exports;
return module.exports;
});
},{}],"assets/tabStops.js":[function(require,module,exports){
/**
* Utility module for handling tabstops tokens generated by Emmet's
* "Expand Abbreviation" action. The main
extract
method will take
* raw text (for example:
${0} some ${1:text}), find all tabstops
* occurrences, replace them with tokens suitable for your editor of choice and
* return object with processed text and list of found tabstops and their ranges.
* For sake of portability (Objective-C/Java) the tabstops list is a plain
* sorted array with plain objects.
*
* Placeholders with the same are meant to be
linked in your editor.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var stringStream = require('./stringStream');
var resources = require('./resources');
/**
* Global placeholder value, automatically incremented by
*
variablesResolver()
function
*/
var startPlaceholderNum = 100;
var tabstopIndex = 0;
var defaultOptions = {
replaceCarets: false,
escape: function(ch) {
return '\\' + ch;
},
tabstop: function(data) {
return data.token;
},
variable: function(data) {
return data.token;
}
};
return {
/**
* Main function that looks for a tabstops in provided
text
* and returns a processed version of
text
with expanded
* placeholders and list of tabstops found.
* @param {String} text Text to process
* @param {Object} options List of processor options:
*
*
replaceCarets :
Boolean
— replace all default
* caret placeholders (like
{%::emmet-caret::%}) with
${0:caret}
*
*
escape :
Function
— function that handle escaped
* characters (mostly '$'). By default, it returns the character itself
* to be displayed as is in output, but sometimes you will use
*
extract
method as intermediate solution for further
* processing and want to keep character escaped. Thus, you should override
*
escape
method to return escaped symbol (e.g. '\\$')
*
*
tabstop :
Function
– a tabstop handler. Receives
* a single argument – an object describing token: its position, number
* group, placeholder and token itself. Should return a replacement
* string that will appear in final output
*
*
variable :
Function
– variable handler. Receives
* a single argument – an object describing token: its position, name
* and original token itself. Should return a replacement
* string that will appear in final output
*
* @returns {Object} Object with processed
text
property
* and array of
tabstops
found
* @memberOf tabStops
*/
extract: function(text, options) {
// prepare defaults
var placeholders = {carets: ''};
var marks = [];
options = utils.extend({}, defaultOptions, options, {
tabstop: function(data) {
var token = data.token;
var ret = '';
if (data.placeholder == 'cursor') {
marks.push({
start: data.start,
end: data.start + token.length,
group: 'carets',
value: ''
});
} else {
// unify placeholder value for single group
if ('placeholder' in data)
placeholders[data.group] = data.placeholder;
if (data.group in placeholders)
ret = placeholders[data.group];
marks.push({
start: data.start,
end: data.start + token.length,
group: data.group,
value: ret
});
}
return token;
}
});
if (options.replaceCarets) {
text = text.replace(new RegExp( utils.escapeForRegexp( utils.getCaretPlaceholder() ), 'g'), '${0:cursor}');
}
// locate tabstops and unify group's placeholders
text = this.processText(text, options);
// now, replace all tabstops with placeholders
var buf = '', lastIx = 0;
var tabStops = marks.map(function(mark) {
buf += text.substring(lastIx, mark.start);
var pos = buf.length;
var ph = placeholders[mark.group] || '';
buf += ph;
lastIx = mark.end;
return {
group: mark.group,
start: pos,
end: pos + ph.length
};
});
buf += text.substring(lastIx);
return {
text: buf,
tabstops: tabStops.sort(function(a, b) {
return a.start - b.start;
})
};
},
/**
* Text processing routine. Locates escaped characters and tabstops and
* replaces them with values returned by handlers defined in
*
options
* @param {String} text
* @param {Object} options See
extract
method options
* description
* @returns {String}
*/
processText: function(text, options) {
options = utils.extend({}, defaultOptions, options);
var buf = '';
/** @type StringStream */
var stream = stringStream.create(text);
var ch, m, a;
while ((ch = stream.next())) {
if (ch == '\\' && !stream.eol()) {
// handle escaped character
buf += options.escape(stream.next());
continue;
}
a = ch;
if (ch == '$') {
// looks like a tabstop
stream.start = stream.pos - 1;
if ((m = stream.match(/^[0-9]+/))) {
// it's $N
a = options.tabstop({
start: buf.length,
group: stream.current().substr(1),
token: stream.current()
});
} else if ((m = stream.match(/^\{([a-z_\-][\w\-]*)\}/))) {
// ${variable}
a = options.variable({
start: buf.length,
name: m[1],
token: stream.current()
});
} else if ((m = stream.match(/^\{([0-9]+)(:.+?)?\}/, false))) {
// ${N:value} or ${N} placeholder
// parse placeholder, including nested ones
stream.skipToPair('{', '}');
var obj = {
start: buf.length,
group: m[1],
token: stream.current()
};
var placeholder = obj.token.substring(obj.group.length + 2, obj.token.length - 1);
if (placeholder) {
obj.placeholder = placeholder.substr(1);
}
a = options.tabstop(obj);
}
}
buf += a;
}
return buf;
},
/**
* Upgrades tabstops in output node in order to prevent naming conflicts
* @param {AbbreviationNode} node
* @param {Number} offset Tab index offset
* @returns {Number} Maximum tabstop index in element
*/
upgrade: function(node, offset) {
var maxNum = 0;
var options = {
tabstop: function(data) {
var group = parseInt(data.group, 10);
if (group > maxNum) maxNum = group;
if (data.placeholder)
return '${' + (group + offset) + ':' + data.placeholder + '}';
else
return '${' + (group + offset) + '}';
}
};
['start', 'end', 'content'].forEach(function(p) {
node[p] = this.processText(node[p], options);
}, this);
return maxNum;
},
/**
* Helper function that produces a callback function for
*
replaceVariables()
method from {@link utils}
* module. This callback will replace variable definitions (like
* ${var_name}) with their value defined in
resource module,
* or outputs tabstop with variable name otherwise.
* @param {AbbreviationNode} node Context node
* @returns {Function}
*/
variablesResolver: function(node) {
var placeholderMemo = {};
return function(str, varName) {
// do not mark `child` variable as placeholder – it‘s a reserved
// variable name
if (varName == 'child') {
return str;
}
if (varName == 'cursor') {
return utils.getCaretPlaceholder();
}
var attr = node.attribute(varName);
if (typeof attr !== 'undefined' && attr !== str) {
return attr;
}
var varValue = resources.getVariable(varName);
if (varValue) {
return varValue;
}
// output as placeholder
if (!placeholderMemo[varName]) {
placeholderMemo[varName] = startPlaceholderNum++;
}
return '${' + placeholderMemo[varName] + ':' + varName + '}';
};
},
/**
* Replace variables like ${var} in string
* @param {String} str
* @param {Object} vars Variable set (defaults to variables defined in
*
snippets.json
) or variable resolver (
Function
)
* @return {String}
*/
replaceVariables: function(str, vars) {
vars = vars || {};
var resolver = typeof vars === 'function' ? vars : function(str, p1) {
return p1 in vars ? vars[p1] : null;
};
return this.processText(str, {
variable: function(data) {
var newValue = resolver(data.token, data.name, data);
if (newValue === null) {
// try to find variable in resources
newValue = resources.getVariable(data.name);
}
if (newValue === null || typeof newValue === 'undefined')
// nothing found, return token itself
newValue = data.token;
return newValue;
}
});
},
/**
* Resets global tabstop index. When parsed tree is converted to output
* string (
AbbreviationNode.toString()
), all tabstops
* defined in snippets and elements are upgraded in order to prevent
* naming conflicts of nested. For example,
${1}
of a node
* should not be linked with the same placehilder of the child node.
* By default,
AbbreviationNode.toString()
automatically
* upgrades tabstops of the same index for each node and writes maximum
* tabstop index into the
tabstopIndex
variable. To keep
* this variable at reasonable value, it is recommended to call
*
resetTabstopIndex()
method each time you expand variable
* @returns
*/
resetTabstopIndex: function() {
tabstopIndex = 0;
startPlaceholderNum = 100;
},
/**
* Output processor for abbreviation parser that will upgrade tabstops
* of parsed node in order to prevent tabstop index conflicts
*/
abbrOutputProcessor: function(text, node, type) {
var maxNum = 0;
var that = this;
var tsOptions = {
tabstop: function(data) {
var group = parseInt(data.group, 10);
if (group === 0)
return '${0}';
if (group > maxNum) maxNum = group;
if (data.placeholder) {
// respect nested placeholders
var ix = group + tabstopIndex;
var placeholder = that.processText(data.placeholder, tsOptions);
return '${' + ix + ':' + placeholder + '}';
} else {
return '${' + (group + tabstopIndex) + '}';
}
}
};
// upgrade tabstops
text = this.processText(text, tsOptions);
// resolve variables
text = this.replaceVariables(text, this.variablesResolver(node));
tabstopIndex += maxNum + 1;
return text;
}
};
});
},{"../utils/common":"utils/common.js","./resources":"assets/resources.js","./stringStream":"assets/stringStream.js"}],"assets/tokenIterator.js":[function(require,module,exports){
/**
* Helper class for convenient token iteration
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
/**
* @type TokenIterator
* @param {Array} tokens
* @type TokenIterator
* @constructor
*/
function TokenIterator(tokens) {
/** @type Array */
this.tokens = tokens;
this._position = 0;
this.reset();
}
TokenIterator.prototype = {
next: function() {
if (this.hasNext()) {
var token = this.tokens[++this._i];
this._position = token.start;
return token;
} else {
this._i = this._il;
}
return null;
},
current: function() {
return this.tokens[this._i];
},
peek: function() {
return this.tokens[this._i + i];
},
position: function() {
return this._position;
},
hasNext: function() {
return this._i < this._il - 1;
},
reset: function() {
this._i = 0;
this._il = this.tokens.length;
},
item: function() {
return this.tokens[this._i];
},
itemNext: function() {
return this.tokens[this._i + 1];
},
itemPrev: function() {
return this.tokens[this._i - 1];
},
nextUntil: function(type, callback) {
var token;
var test = typeof type == 'string'
? function(t){return t.type == type;}
: type;
while ((token = this.next())) {
if (callback)
callback.call(this, token);
if (test.call(this, token))
break;
}
}
};
return {
create: function(tokens) {
return new TokenIterator(tokens);
}
};
});
},{}],"editTree/base.js":[function(require,module,exports){
/**
* Abstract implementation of edit tree interface.
* Edit tree is a named container of editable “name-value” child elements,
* parsed from
source
. This container provides convenient methods
* for editing/adding/removing child elements. All these update actions are
* instantly reflected in the
source
code with respect of formatting.
*
* For example, developer can create an edit tree from CSS rule and add or
* remove properties from it–all changes will be immediately reflected in the
* original source.
*
* All classes defined in this module should be extended the same way as in
* Backbone framework: using
extend
method to create new class and
*
initialize
method to define custom class constructor.
*
* @example
*
* var MyClass = require('editTree/base').EditElement.extend({
* initialize: function() {
* // constructor code here
* }
* });
*
* var elem = new MyClass();
*
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var range = require('../assets/range');
var utils = require('../utils/common');
var klass = require('../vendor/klass');
/**
* Named container of edited source
* @type EditContainer
* @param {String} source
* @param {Object} options
*/
function EditContainer(source, options) {
this.options = utils.extend({offset: 0}, options);
/**
* Source code of edited structure. All changes in the structure are
* immediately reflected into this property
*/
this.source = source;
/**
* List of all editable children
* @private
*/
this._children = [];
/**
* Hash of all positions of container
* @private
*/
this._positions = {
name: 0
};
this.initialize.apply(this, arguments);
}
/**
* The self-propagating extend function for classes.
* @type Function
*/
EditContainer.extend = klass.extend;
EditContainer.prototype = {
type: 'container',
/**
* Child class constructor
*/
initialize: function() {},
/**
* Make position absolute
* @private
* @param {Number} num
* @param {Boolean} isAbsolute
* @returns {Boolean}
*/
_pos: function(num, isAbsolute) {
return num + (isAbsolute ? this.options.offset : 0);
},
/**
* Replace substring of tag's source
* @param {String} value
* @param {Number} start
* @param {Number} end
* @private
*/
_updateSource: function(value, start, end) {
// create modification range
var r = range.create(start, typeof end === 'undefined' ? 0 : end - start);
var delta = value.length - r.length();
var update = function(obj) {
Object.keys(obj).forEach(function(k) {
if (obj[k] >= r.end) {
obj[k] += delta;
}
});
};
// update affected positions of current container
update(this._positions);
// update affected positions of children
var recursiveUpdate = function(items) {
items.forEach(function(item) {
update(item._positions);
if (item.type == 'container') {
recursiveUpdate(item.list());
}
});
};
recursiveUpdate(this.list());
this.source = utils.replaceSubstring(this.source, value, r);
},
/**
* Adds new attribute
* @param {String} name Property name
* @param {String} value Property value
* @param {Number} pos Position at which to insert new property. By
* default the property is inserted at the end of rule
* @returns {EditElement} Newly created element
*/
add: function(name, value, pos) {
// this is abstract implementation
var item = new EditElement(name, value);
this._children.push(item);
return item;
},
/**
* Returns attribute object
* @param {String} name Attribute name or its index
* @returns {EditElement}
*/
get: function(name) {
if (typeof name === 'number') {
return this.list()[name];
}
if (typeof name === 'string') {
return utils.find(this.list(), function(prop) {
return prop.name() === name;
});
}
return name;
},
/**
* Returns all children by name or indexes
* @param {Object} name Element name(s) or indexes (
String
,
*
Array
,
Number
)
* @returns {Array}
*/
getAll: function(name) {
if (!Array.isArray(name))
name = [name];
// split names and indexes
var names = [], indexes = [];
name.forEach(function(item) {
if (typeof item === 'string') {
names.push(item);
} else if (typeof item === 'number') {
indexes.push(item);
}
});
return this.list().filter(function(attribute, i) {
return ~indexes.indexOf(i) || ~names.indexOf(attribute.name());
});
},
/**
* Returns list of all editable child elements
* @returns {Array}
*/
list: function() {
return this._children;
},
/**
* Remove child element
* @param {String} name Property name or its index
*/
remove: function(name) {
var element = this.get(name);
if (element) {
this._updateSource('', element.fullRange());
var ix = this._children.indexOf(element);
if (~ix) {
this._children.splice(ix, 1);
}
}
},
/**
* Returns index of editble child in list
* @param {Object} item
* @returns {Number}
*/
indexOf: function(item) {
return this.list().indexOf(this.get(item));
},
/**
* Returns or updates element value. If such element doesn't exists,
* it will be created automatically and added at the end of child list.
* @param {String} name Element name or its index
* @param {String} value New element value
* @returns {String}
*/
value: function(name, value, pos) {
var element = this.get(name);
if (element)
return element.value(value);
if (typeof value !== 'undefined') {
// no such element — create it
return this.add(name, value, pos);
}
},
/**
* Returns all values of child elements found by
getAll()
* method
* @param {Object} name Element name(s) or indexes (
String
,
*
Array
,
Number
)
* @returns {Array}
*/
values: function(name) {
return this.getAll(name).map(function(element) {
return element.value();
});
},
/**
* Sets or gets container name
* @param {String} val New name. If not passed, current
* name is returned
* @return {String}
*/
name: function(val) {
if (typeof val !== 'undefined' && this._name !== (val = String(val))) {
this._updateSource(val, this._positions.name, this._positions.name + this._name.length);
this._name = val;
}
return this._name;
},
/**
* Returns name range object
* @param {Boolean} isAbsolute Return absolute range (with respect of
* rule offset)
* @returns {Range}
*/
nameRange: function(isAbsolute) {
return range.create(this._positions.name + (isAbsolute ? this.options.offset : 0), this.name());
},
/**
* Returns range of current source
* @param {Boolean} isAbsolute
*/
range: function(isAbsolute) {
return range.create(isAbsolute ? this.options.offset : 0, this.valueOf());
},
/**
* Returns element that belongs to specified position
* @param {Number} pos
* @param {Boolean} isAbsolute
* @returns {EditElement}
*/
itemFromPosition: function(pos, isAbsolute) {
return utils.find(this.list(), function(elem) {
return elem.range(isAbsolute).inside(pos);
});
},
/**
* Returns source code of current container
* @returns {String}
*/
toString: function() {
return this.valueOf();
},
valueOf: function() {
return this.source;
}
};
/**
* @param {EditContainer} parent
* @param {Object} nameToken
* @param {Object} valueToken
*/
function EditElement(parent, nameToken, valueToken) {
/** @type EditContainer */
this.parent = parent;
this._name = nameToken.value;
this._value = valueToken ? valueToken.value : '';
this._positions = {
name: nameToken.start,
value: valueToken ? valueToken.start : -1
};
this.initialize.apply(this, arguments);
}
/**
* The self-propagating extend function for classes.
* @type Function
*/
EditElement.extend = klass.extend;
EditElement.prototype = {
type: 'element',
/**
* Child class constructor
*/
initialize: function() {},
/**
* Make position absolute
* @private
* @param {Number} num
* @param {Boolean} isAbsolute
* @returns {Boolean}
*/
_pos: function(num, isAbsolute) {
return num + (isAbsolute ? this.parent.options.offset : 0);
},
/**
* Sets of gets element value
* @param {String} val New element value. If not passed, current
* value is returned
* @returns {String}
*/
value: function(val) {
if (typeof val !== 'undefined' && this._value !== (val = String(val))) {
this.parent._updateSource(val, this.valueRange());
this._value = val;
}
return this._value;
},
/**
* Sets of gets element name
* @param {String} val New element name. If not passed, current
* name is returned
* @returns {String}
*/
name: function(val) {
if (typeof val !== 'undefined' && this._name !== (val = String(val))) {
this.parent._updateSource(val, this.nameRange());
this._name = val;
}
return this._name;
},
/**
* Returns position of element name token
* @param {Boolean} isAbsolute Return absolute position
* @returns {Number}
*/
namePosition: function(isAbsolute) {
return this._pos(this._positions.name, isAbsolute);
},
/**
* Returns position of element value token
* @param {Boolean} isAbsolute Return absolute position
* @returns {Number}
*/
valuePosition: function(isAbsolute) {
return this._pos(this._positions.value, isAbsolute);
},
/**
* Returns element name
* @param {Boolean} isAbsolute Return absolute range
* @returns {Range}
*/
range: function(isAbsolute) {
return range.create(this.namePosition(isAbsolute), this.valueOf());
},
/**
* Returns full element range, including possible indentation
* @param {Boolean} isAbsolute Return absolute range
* @returns {Range}
*/
fullRange: function(isAbsolute) {
return this.range(isAbsolute);
},
/**
* Returns element name range
* @param {Boolean} isAbsolute Return absolute range
* @returns {Range}
*/
nameRange: function(isAbsolute) {
return range.create(this.namePosition(isAbsolute), this.name());
},
/**
* Returns element value range
* @param {Boolean} isAbsolute Return absolute range
* @returns {Range}
*/
valueRange: function(isAbsolute) {
return range.create(this.valuePosition(isAbsolute), this.value());
},
/**
* Returns current element string representation
* @returns {String}
*/
toString: function() {
return this.valueOf();
},
valueOf: function() {
return this.name() + this.value();
}
};
return {
EditContainer: EditContainer,
EditElement: EditElement,
/**
* Creates token that can be fed to
EditElement
* @param {Number} start
* @param {String} value
* @param {String} type
* @returns
*/
createToken: function(start, value, type) {
var obj = {
start: start || 0,
value: value || '',
type: type
};
obj.end = obj.start + obj.value.length;
return obj;
}
};
});
},{"../assets/range":"assets/range.js","../utils/common":"utils/common.js","../vendor/klass":"vendor/klass.js"}],"editTree/css.js":[function(require,module,exports){
/**
* CSS EditTree is a module that can parse a CSS rule into a tree with
* convenient methods for adding, modifying and removing CSS properties. These
* changes can be written back to string with respect of code formatting.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var editTree = require('./base');
var cssParser = require('../parser/css');
var cssSections = require('../utils/cssSections');
var range = require('../assets/range');
var stringStream = require('../assets/stringStream');
var tokenIterator = require('../assets/tokenIterator');
var defaultOptions = {
styleBefore: '\n\t',
styleSeparator: ': ',
offset: 0
};
var reSpaceStart = /^\s+/;
var reSpaceEnd = /\s+$/;
var WHITESPACE_REMOVE_FROM_START = 1;
var WHITESPACE_REMOVE_FROM_END = 2;
/**
* Modifies given range to remove whitespace from beginning
* and/or from the end
* @param {Range} rng Range to modify
* @param {String} text Text that range belongs to
* @param {Number} mask Mask indicating from which end
* whitespace should be removed
* @return {Range}
*/
function trimWhitespaceInRange(rng, text, mask) {
mask = mask || (WHITESPACE_REMOVE_FROM_START | WHITESPACE_REMOVE_FROM_END);
text = rng.substring(text);
var m;
if ((mask & WHITESPACE_REMOVE_FROM_START) && (m = text.match(reSpaceStart))) {
rng.start += m[0].length;
}
if ((mask & WHITESPACE_REMOVE_FROM_END) && (m = text.match(reSpaceEnd))) {
rng.end -= m[0].length;
}
// in case given range is just a whatespace
if (rng.end < rng.start) {
rng.end = rng.start;
}
return rng;
}
/**
* Consumes CSS property and value from current token
* iterator state. Offsets iterator pointer into token
* that can be used for next value consmption
* @param {TokenIterator} it
* @param {String} text
* @return {Object} Object with `name` and `value` properties
* ar ranges. Value range can be zero-length.
*/
function consumeSingleProperty(it, text) {
var name, value, end;
var token = it.current();
if (!token) {
return null;
}
// skip whitespace
var ws = {'white': 1, 'line': 1, 'comment': 1};
while ((token = it.current())) {
if (!(token.type in ws)) {
break;
}
it.next();
}
if (!it.hasNext()) {
return null;
}
// consume property name
token = it.current();
name = range(token.start, token.value);
var isAtProperty = token.value.charAt(0) == '@';
while (token = it.next()) {
name.end = token.end;
if (token.type == ':' || token.type == 'white') {
name.end = token.start;
it.next();
if (token.type == ':' || isAtProperty) {
// XXX I really ashame of this hardcode, but I need
// to stop parsing if this is an SCSS mixin call,
// for example: @include border-radius(10px)
break;
}
} else if (token.type == ';' || token.type == 'line') {
// there’s no value, looks like a mixin
// or a special use case:
// user is writing a new property or abbreviation
name.end = token.start;
value = range(token.start, 0);
it.next();
break;
}
}
token = it.current();
if (!value && token) {
if (token.type == 'line') {
lastNewline = token;
}
// consume value
value = range(token.start, token.value);
var lastNewline;
while ((token = it.next())) {
value.end = token.end;
if (token.type == 'line') {
lastNewline = token;
} else if (token.type == '}' || token.type == ';') {
value.end = token.start;
if (token.type == ';') {
end = range(token.start, token.value);
}
it.next();
break;
} else if (token.type == ':' && lastNewline) {
// A special case:
// user is writing a value before existing
// property, but didn’t inserted closing semi-colon.
// In this case, limit value range to previous
// newline
value.end = lastNewline.start;
it._i = it.tokens.indexOf(lastNewline);
break;
}
}
}
if (!value) {
value = range(name.end, 0);
}
return {
name: trimWhitespaceInRange(name, text),
value: trimWhitespaceInRange(value, text, WHITESPACE_REMOVE_FROM_START | (end ? WHITESPACE_REMOVE_FROM_END : 0)),
end: end || range(value.end, 0)
};
}
/**
* Finds parts of complex CSS value
* @param {String} str
* @returns {Array} Returns list of
Range
's
*/
function findParts(str) {
/** @type StringStream */
var stream = stringStream.create(str);
var ch;
var result = [];
var sep = /[\s\u00a0,;]/;
var add = function() {
stream.next();
result.push(range(stream.start, stream.current()));
stream.start = stream.pos;
};
// skip whitespace
stream.eatSpace();
stream.start = stream.pos;
while ((ch = stream.next())) {
if (ch == '"' || ch == "'") {
stream.next();
if (!stream.skipTo(ch)) break;
add();
} else if (ch == '(') {
// function found, may have nested function
stream.backUp(1);
if (!stream.skipToPair('(', ')')) break;
stream.backUp(1);
add();
} else {
if (sep.test(ch)) {
result.push(range(stream.start, stream.current().length - 1));
stream.eatWhile(sep);
stream.start = stream.pos;
}
}
}
add();
return utils.unique(result.filter(function(item) {
return !!item.length();
}));
}
/**
* Parses CSS properties from given CSS source
* and adds them to CSSEditContainer node
* @param {CSSEditContainer} node
* @param {String} source CSS source
* @param {Number} offset Offset of properties subset from original source
*/
function consumeProperties(node, source, offset) {
var list = extractPropertiesFromSource(source, offset);
list.forEach(function(property) {
node._children.push(new CSSEditElement(node,
editTree.createToken(property.name.start, property.nameText),
editTree.createToken(property.value.start, property.valueText),
editTree.createToken(property.end.start, property.endText)
));
});
}
/**
* Parses given CSS source and returns list of ranges of located CSS properties.
* Normally, CSS source must contain properties only, it must be,
* for example, a content of CSS selector or text between nested
* CSS sections
* @param {String} source CSS source
* @param {Number} offset Offset of properties subset from original source.
* Used to provide proper ranges of locates items
*/
function extractPropertiesFromSource(source, offset) {
offset = offset || 0;
source = source.replace(reSpaceEnd, '');
var out = [];
if (!source) {
return out;
}
var tokens = cssParser.parse(source);
var it = tokenIterator.create(tokens);
var property;
while ((property = consumeSingleProperty(it, source))) {
out.push({
nameText: property.name.substring(source),
name: property.name.shift(offset),
valueText: property.value.substring(source),
value: property.value.shift(offset),
endText: property.end.substring(source),
end: property.end.shift(offset)
});
}
return out;
}
/**
* @class
* @extends EditContainer
*/
var CSSEditContainer = editTree.EditContainer.extend({
initialize: function(source, options) {
utils.extend(this.options, defaultOptions, options);
if (Array.isArray(source)) {
source = cssParser.toSource(source);
}
var allRules = cssSections.findAllRules(source);
var currentRule = allRules.shift();
// keep top-level rules only since they will
// be parsed by nested CSSEditContainer call
var topLevelRules = [];
allRules.forEach(function(r) {
var isTopLevel = !utils.find(topLevelRules, function(tr) {
return tr.contains(r);
});
if (isTopLevel) {
topLevelRules.push(r);
}
});
var selectorRange = range.create2(currentRule.start, currentRule._selectorEnd);
this._name = selectorRange.substring(source);
this._positions.name = selectorRange.start;
this._positions.contentStart = currentRule._contentStart + 1;
var sectionOffset = currentRule._contentStart + 1;
var sectionEnd = currentRule.end - 1;
// parse properties between nested rules
// and add nested rules as children
var that = this;
topLevelRules.forEach(function(r) {
consumeProperties(that, source.substring(sectionOffset, r.start), sectionOffset);
var opt = utils.extend({}, that.options, {offset: r.start + that.options.offset});
// XXX I think I don’t need nested containers here
// They should be handled separately
// that._children.push(new CSSEditContainer(r.substring(source), opt));
sectionOffset = r.end;
});
// consume the rest of data
consumeProperties(this, source.substring(sectionOffset, currentRule.end - 1), sectionOffset);
this._saveStyle();
},
/**
* Remembers all styles of properties
* @private
*/
_saveStyle: function() {
var start = this._positions.contentStart;
var source = this.source;
this.list().forEach(function(p) {
if (p.type === 'container') {
return;
}
p.styleBefore = source.substring(start, p.namePosition());
// a small hack here:
// Sometimes users add empty lines before properties to logically
// separate groups of properties. In this case, a blind copy of
// characters between rules may lead to undesired behavior,
// especially when current rule is duplicated or used as a donor
// to create new rule.
// To solve this issue, we‘ll take only last newline indentation
var lines = utils.splitByLines(p.styleBefore);
if (lines.length > 1) {
p.styleBefore = '\n' + lines[lines.length - 1];
}
p.styleSeparator = source.substring(p.nameRange().end, p.valuePosition());
// graceful and naive comments removal
var parts = p.styleBefore.split('*/');
p.styleBefore = parts[parts.length - 1];
p.styleSeparator = p.styleSeparator.replace(/\/\*.*?\*\//g, '');
start = p.range().end;
});
},
/**
* Returns position of element name token
* @param {Boolean} isAbsolute Return absolute position
* @returns {Number}
*/
namePosition: function(isAbsolute) {
return this._pos(this._positions.name, isAbsolute);
},
/**
* Returns position of element value token
* @param {Boolean} isAbsolute Return absolute position
* @returns {Number}
*/
valuePosition: function(isAbsolute) {
return this._pos(this._positions.contentStart, isAbsolute);
},
/**
* Returns element value range
* @param {Boolean} isAbsolute Return absolute range
* @returns {Range}
*/
valueRange: function(isAbsolute) {
return range.create2(this.valuePosition(isAbsolute), this._pos(this.valueOf().length, isAbsolute) - 1);
},
/**
* Adds new CSS property
* @param {String} name Property name
* @param {String} value Property value
* @param {Number} pos Position at which to insert new property. By
* default the property is inserted at the end of rule
* @returns {CSSEditProperty}
*/
add: function(name, value, pos) {
var list = this.list();
var start = this._positions.contentStart;
var styles = utils.pick(this.options, 'styleBefore', 'styleSeparator');
if (typeof pos === 'undefined') {
pos = list.length;
}
/** @type CSSEditProperty */
var donor = list[pos];
if (donor) {
start = donor.fullRange().start;
} else if ((donor = list[pos - 1])) {
// make sure that donor has terminating semicolon
donor.end(';');
start = donor.range().end;
}
if (donor) {
styles = utils.pick(donor, 'styleBefore', 'styleSeparator');
}
var nameToken = editTree.createToken(start + styles.styleBefore.length, name);
var valueToken = editTree.createToken(nameToken.end + styles.styleSeparator.length, value);
var property = new CSSEditElement(this, nameToken, valueToken,
editTree.createToken(valueToken.end, ';'));
utils.extend(property, styles);
// write new property into the source
this._updateSource(property.styleBefore + property.toString(), start);
// insert new property
this._children.splice(pos, 0, property);
return property;
}
});
/**
* @class
* @type CSSEditElement
* @constructor
*/
var CSSEditElement = editTree.EditElement.extend({
initialize: function(rule, name, value, end) {
this.styleBefore = rule.options.styleBefore;
this.styleSeparator = rule.options.styleSeparator;
this._end = end.value;
this._positions.end = end.start;
},
/**
* Returns ranges of complex value parts
* @returns {Array} Returns
null
if value is not complex
*/
valueParts: function(isAbsolute) {
var parts = findParts(this.value());
if (isAbsolute) {
var offset = this.valuePosition(true);
parts.forEach(function(p) {
p.shift(offset);
});
}
return parts;
},
/**
* Sets of gets element value.
* When setting value, this implementation will ensure that your have
* proper name-value separator
* @param {String} val New element value. If not passed, current
* value is returned
* @returns {String}
*/
value: function(val) {
var isUpdating = typeof val !== 'undefined';
var allItems = this.parent.list();
if (isUpdating && this.isIncomplete()) {
var self = this;
var donor = utils.find(allItems, function(item) {
return item !== self && !item.isIncomplete();
});
this.styleSeparator = donor
? donor.styleSeparator
: this.parent.options.styleSeparator;
this.parent._updateSource(this.styleSeparator, range(this.valueRange().start, 0));
}
var value = this.constructor.__super__.value.apply(this, arguments);
if (isUpdating) {
// make sure current property has terminating semi-colon
// if it’s not the last one
var ix = allItems.indexOf(this);
if (ix !== allItems.length - 1 && !this.end()) {
this.end(';');
}
}
return value;
},
/**
* Test if current element is incomplete, e.g. has no explicit
* name-value separator
* @return {Boolean} [description]
*/
isIncomplete: function() {
return this.nameRange().end === this.valueRange().start;
},
/**
* Sets of gets property end value (basically, it's a semicolon)
* @param {String} val New end value. If not passed, current
* value is returned
*/
end: function(val) {
if (typeof val !== 'undefined' && this._end !== val) {
this.parent._updateSource(val, this._positions.end, this._positions.end + this._end.length);
this._end = val;
}
return this._end;
},
/**
* Returns full rule range, with indentation
* @param {Boolean} isAbsolute Return absolute range (with respect of
* rule offset)
* @returns {Range}
*/
fullRange: function(isAbsolute) {
var r = this.range(isAbsolute);
r.start -= this.styleBefore.length;
return r;
},
/**
* Returns item string representation
* @returns {String}
*/
valueOf: function() {
return this.name() + this.styleSeparator + this.value() + this.end();
}
});
return {
/**
* Parses CSS rule into editable tree
* @param {String} source
* @param {Object} options
* @memberOf emmet.cssEditTree
* @returns {EditContainer}
*/
parse: function(source, options) {
return new CSSEditContainer(source, options);
},
/**
* Extract and parse CSS rule from specified position in
content
* @param {String} content CSS source code
* @param {Number} pos Character position where to start source code extraction
* @returns {EditContainer}
*/
parseFromPosition: function(content, pos, isBackward) {
var bounds = cssSections.locateRule(content, pos, isBackward);
if (!bounds || !bounds.inside(pos)) {
// no matching CSS rule or caret outside rule bounds
return null;
}
return this.parse(bounds.substring(content), {
offset: bounds.start
});
},
/**
* Locates CSS property in given CSS code fragment under specified character position
* @param {String} css CSS code or parsed CSSEditContainer
* @param {Number} pos Character position where to search CSS property
* @return {CSSEditElement}
*/
propertyFromPosition: function(css, pos) {
var cssProp = null;
/** @type EditContainer */
var cssRule = typeof css === 'string' ? this.parseFromPosition(css, pos, true) : css;
if (cssRule) {
cssProp = cssRule.itemFromPosition(pos, true);
if (!cssProp) {
// in case user just started writing CSS property
// and didn't include semicolon–try another approach
cssProp = utils.find(cssRule.list(), function(elem) {
return elem.range(true).end == pos;
});
}
}
return cssProp;
},
/**
* Removes vendor prefix from CSS property
* @param {String} name CSS property
* @return {String}
*/
baseName: function(name) {
return name.replace(/^\s*\-\w+\-/, '');
},
/**
* Finds parts of complex CSS value
* @param {String} str
* @returns {Array}
*/
findParts: findParts,
extractPropertiesFromSource: extractPropertiesFromSource
};
});
},{"../assets/range":"assets/range.js","../assets/stringStream":"assets/stringStream.js","../assets/tokenIterator":"assets/tokenIterator.js","../parser/css":"parser/css.js","../utils/common":"utils/common.js","../utils/cssSections":"utils/cssSections.js","./base":"editTree/base.js"}],"editTree/xml.js":[function(require,module,exports){
/**
* XML EditTree is a module that can parse an XML/HTML element into a tree with
* convenient methods for adding, modifying and removing attributes. These
* changes can be written back to string with respect of code formatting.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var editTree = require('./base');
var xmlParser = require('../parser/xml');
var range = require('../assets/range');
var utils = require('../utils/common');
var defaultOptions = {
styleBefore: ' ',
styleSeparator: '=',
styleQuote: '"',
offset: 0
};
var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/m;
var XMLEditContainer = editTree.EditContainer.extend({
initialize: function(source, options) {
utils.defaults(this.options, defaultOptions);
this._positions.name = 1;
var attrToken = null;
var tokens = xmlParser.parse(source);
tokens.forEach(function(token) {
token.value = range.create(token).substring(source);
switch (token.type) {
case 'tag':
if (/^<[^\/]+/.test(token.value)) {
this._name = token.value.substring(1);
}
break;
case 'attribute':
// add empty attribute
if (attrToken) {
this._children.push(new XMLEditElement(this, attrToken));
}
attrToken = token;
break;
case 'string':
this._children.push(new XMLEditElement(this, attrToken, token));
attrToken = null;
break;
}
}, this);
if (attrToken) {
this._children.push(new XMLEditElement(this, attrToken));
}
this._saveStyle();
},
/**
* Remembers all styles of properties
* @private
*/
_saveStyle: function() {
var start = this.nameRange().end;
var source = this.source;
this.list().forEach(function(p) {
p.styleBefore = source.substring(start, p.namePosition());
if (p.valuePosition() !== -1) {
p.styleSeparator = source.substring(p.namePosition() + p.name().length, p.valuePosition() - p.styleQuote.length);
}
start = p.range().end;
});
},
/**
* Adds new attribute
* @param {String} name Property name
* @param {String} value Property value
* @param {Number} pos Position at which to insert new property. By
* default the property is inserted at the end of rule
*/
add: function(name, value, pos) {
var list = this.list();
var start = this.nameRange().end;
var styles = utils.pick(this.options, 'styleBefore', 'styleSeparator', 'styleQuote');
if (typeof pos === 'undefined') {
pos = list.length;
}
/** @type XMLEditAttribute */
var donor = list[pos];
if (donor) {
start = donor.fullRange().start;
} else if ((donor = list[pos - 1])) {
start = donor.range().end;
}
if (donor) {
styles = utils.pick(donor, 'styleBefore', 'styleSeparator', 'styleQuote');
}
value = styles.styleQuote + value + styles.styleQuote;
var attribute = new XMLEditElement(this,
editTree.createToken(start + styles.styleBefore.length, name),
editTree.createToken(start + styles.styleBefore.length + name.length
+ styles.styleSeparator.length, value)
);
utils.extend(attribute, styles);
// write new attribute into the source
this._updateSource(attribute.styleBefore + attribute.toString(), start);
// insert new attribute
this._children.splice(pos, 0, attribute);
return attribute;
},
/**
* A special case of attribute editing: adds class value to existing
* `class` attribute
* @param {String} value
*/
addClass: function(value) {
var attr = this.get('class');
value = utils.trim(value);
if (!attr) {
return this.add('class', value);
}
var classVal = attr.value();
var classList = ' ' + classVal.replace(/\n/g, ' ') + ' ';
if (!~classList.indexOf(' ' + value + ' ')) {
attr.value(classVal + ' ' + value);
}
},
/**
* A special case of attribute editing: removes class value from existing
* `class` attribute
* @param {String} value
*/
removeClass: function(value) {
var attr = this.get('class');
value = utils.trim(value);
if (!attr) {
return;
}
var reClass = new RegExp('(^|\\s+)' + utils.escapeForRegexp(value));
var classVal = attr.value().replace(reClass, '');
if (!utils.trim(classVal)) {
this.remove('class');
} else {
attr.value(classVal);
}
}
});
var XMLEditElement = editTree.EditElement.extend({
initialize: function(parent, nameToken, valueToken) {
this.styleBefore = parent.options.styleBefore;
this.styleSeparator = parent.options.styleSeparator;
var value = '', quote = parent.options.styleQuote;
if (valueToken) {
value = valueToken.value;
quote = value.charAt(0);
if (quote == '"' || quote == "'") {
value = value.substring(1);
} else {
quote = '';
}
if (quote && value.charAt(value.length - 1) == quote) {
value = value.substring(0, value.length - 1);
}
}
this.styleQuote = quote;
this._value = value;
this._positions.value = valueToken ? valueToken.start + quote.length : -1;
},
/**
* Returns full rule range, with indentation
* @param {Boolean} isAbsolute Return absolute range (with respect of
* rule offset)
* @returns {Range}
*/
fullRange: function(isAbsolute) {
var r = this.range(isAbsolute);
r.start -= this.styleBefore.length;
return r;
},
valueOf: function() {
return this.name() + this.styleSeparator
+ this.styleQuote + this.value() + this.styleQuote;
}
});
return {
/**
* Parses HTML element into editable tree
* @param {String} source
* @param {Object} options
* @memberOf emmet.htmlEditTree
* @returns {EditContainer}
*/
parse: function(source, options) {
return new XMLEditContainer(source, options);
},
/**
* Extract and parse HTML from specified position in
content
* @param {String} content CSS source code
* @param {Number} pos Character position where to start source code extraction
* @returns {XMLEditElement}
*/
parseFromPosition: function(content, pos, isBackward) {
var bounds = this.extractTag(content, pos, isBackward);
if (!bounds || !bounds.inside(pos))
// no matching HTML tag or caret outside tag bounds
return null;
return this.parse(bounds.substring(content), {
offset: bounds.start
});
},
/**
* Extracts nearest HTML tag range from
content
, starting at
*
pos
position
* @param {String} content
* @param {Number} pos
* @param {Boolean} isBackward
* @returns {Range}
*/
extractTag: function(content, pos, isBackward) {
var len = content.length, i;
// max extraction length. I don't think there may be tags larger
// than 2000 characters length
var maxLen = Math.min(2000, len);
/** @type Range */
var r = null;
var match = function(pos) {
var m;
if (content.charAt(pos) == '<' && (m = content.substr(pos, maxLen).match(startTag)))
return range.create(pos, m[0]);
};
// lookup backward, in case we are inside tag already
for (i = pos; i >= 0; i--) {
if ((r = match(i))) break;
}
if (r && (r.inside(pos) || isBackward))
return r;
if (!r && isBackward)
return null;
// search forward
for (i = pos; i < len; i++) {
if ((r = match(i)))
return r;
}
}
};
});
},{"../assets/range":"assets/range.js","../parser/xml":"parser/xml.js","../utils/common":"utils/common.js","./base":"editTree/base.js"}],"filter/bem.js":[function(require,module,exports){
/**
* Filter for aiding of writing elements with complex class names as described
* in Yandex's BEM (Block, Element, Modifier) methodology. This filter will
* automatically inherit block and element names from parent elements and insert
* them into child element classes
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var htmlFilter = require('./html');
var prefs = require('../assets/preferences');
var abbreviationUtils = require('../utils/abbreviation');
var utils = require('../utils/common');
prefs.define('bem.elementSeparator', '__', 'Class name’s element separator.');
prefs.define('bem.modifierSeparator', '_', 'Class name’s modifier separator.');
prefs.define('bem.shortElementPrefix', '-',
'Symbol for describing short “block-element” notation. Class names '
+ 'prefixed with this symbol will be treated as element name for parent‘s '
+ 'block name. Each symbol instance traverses one level up in parsed '
+ 'tree for block name lookup. Empty value will disable short notation.');
var shouldRunHtmlFilter = false;
function getSeparators() {
return {
element: prefs.get('bem.elementSeparator'),
modifier: prefs.get('bem.modifierSeparator')
};
}
/**
* @param {AbbreviationNode} item
*/
function bemParse(item) {
if (abbreviationUtils.isSnippet(item))
return item;
// save BEM stuff in cache for faster lookups
item.__bem = {
block: '',
element: '',
modifier: ''
};
var classNames = normalizeClassName(item.attribute('class')).split(' ');
// guess best match for block name
var reBlockName = /^[a-z]\-/i;
item.__bem.block = utils.find(classNames, function(name) {
return reBlockName.test(name);
});
// guessing doesn't worked, pick first class name as block name
if (!item.__bem.block) {
reBlockName = /^[a-z]/i;
item.__bem.block = utils.find(classNames, function(name) {
return reBlockName.test(name);
}) || '';
}
classNames = classNames.map(function(name) {
return processClassName(name, item);
});
classNames = utils.unique(utils.flatten(classNames)).join(' ');
if (classNames) {
item.attribute('class', classNames);
}
return item;
}
/**
* @param {String} className
* @returns {String}
*/
function normalizeClassName(className) {
className = (' ' + (className || '') + ' ').replace(/\s+/g, ' ');
var shortSymbol = prefs.get('bem.shortElementPrefix');
if (shortSymbol) {
var re = new RegExp('\\s(' + utils.escapeForRegexp(shortSymbol) + '+)', 'g');
className = className.replace(re, function(str, p1) {
return ' ' + utils.repeatString(getSeparators().element, p1.length);
});
}
return utils.trim(className);
}
/**
* Processes class name
* @param {String} name Class name item to process
* @param {AbbreviationNode} item Host node for provided class name
* @returns Processed class name. May return
Array
of
* class names
*/
function processClassName(name, item) {
name = transformClassName(name, item, 'element');
name = transformClassName(name, item, 'modifier');
// expand class name
// possible values:
// * block__element
// * block__element_modifier
// * block__element_modifier1_modifier2
// * block_modifier
var block = '', element = '', modifier = '';
var separators = getSeparators();
if (~name.indexOf(separators.element)) {
var elements = name.split(separators.element);
block = elements.shift();
var modifiers = elements.pop().split(separators.modifier);
elements.push(modifiers.shift());
element = elements.join(separators.element);
modifier = modifiers.join(separators.modifier);
} else if (~name.indexOf(separators.modifier)) {
var blockModifiers = name.split(separators.modifier);
block = blockModifiers.shift();
modifier = blockModifiers.join(separators.modifier);
}
if (block || element || modifier) {
if (!block) {
block = item.__bem.block;
}
// inherit parent bem element, if exists
// if (item.parent && item.parent.__bem && item.parent.__bem.element)
// element = item.parent.__bem.element + separators.element + element;
// produce multiple classes
var prefix = block;
var result = [];
if (element) {
prefix += separators.element + element;
result.push(prefix);
} else {
result.push(prefix);
}
if (modifier) {
result.push(prefix + separators.modifier + modifier);
}
item.__bem.block = block;
item.__bem.element = element;
item.__bem.modifier = modifier;
return result;
}
// ...otherwise, return processed or original class name
return name;
}
/**
* Low-level function to transform user-typed class name into full BEM class
* @param {String} name Class name item to process
* @param {AbbreviationNode} item Host node for provided class name
* @param {String} entityType Type of entity to be tried to transform
* ('element' or 'modifier')
* @returns {String} Processed class name or original one if it can't be
* transformed
*/
function transformClassName(name, item, entityType) {
var separators = getSeparators();
var reSep = new RegExp('^(' + separators[entityType] + ')+', 'g');
if (reSep.test(name)) {
var depth = 0; // parent lookup depth
var cleanName = name.replace(reSep, function(str) {
depth = str.length / separators[entityType].length;
return '';
});
// find donor element
var donor = item;
while (donor.parent && depth--) {
donor = donor.parent;
}
if (!donor || !donor.__bem)
donor = item;
if (donor && donor.__bem) {
var prefix = donor.__bem.block;
// decide if we should inherit element name
// if (entityType == 'element') {
// var curElem = cleanName.split(separators.modifier, 1)[0];
// if (donor.__bem.element && donor.__bem.element != curElem)
// prefix += separators.element + donor.__bem.element;
// }
if (entityType == 'modifier' && donor.__bem.element)
prefix += separators.element + donor.__bem.element;
return prefix + separators[entityType] + cleanName;
}
}
return name;
}
/**
* Recursive function for processing tags, which extends class names
* according to BEM specs: http://bem.github.com/bem-method/pages/beginning/beginning.ru.html
*
* It does several things:
*
* - Expands complex class name (according to BEM symbol semantics):
* .block__elem_modifier → .block.block__elem.block__elem_modifier
*
* - Inherits block name on child elements:
* .b-block > .__el > .__el → .b-block > .b-block__el > .b-block__el__el
*
* - Treats first dash symbol as '__'
* - Double underscore (or typographic '–') is also treated as an element
* level lookup, e.g. ____el will search for element definition in parent’s
* parent element:
* .b-block > .__el1 > .____el2 → .b-block > .b-block__el1 > .b-block__el2
*
*
*
* @param {AbbreviationNode} tree
* @param {Object} profile
*/
function process(tree, profile) {
if (tree.name) {
bemParse(tree, profile);
}
tree.children.forEach(function(item) {
process(item, profile);
if (!abbreviationUtils.isSnippet(item) && item.start) {
shouldRunHtmlFilter = true;
}
});
return tree;
}
return function(tree, profile) {
shouldRunHtmlFilter = false;
tree = process(tree, profile);
// in case 'bem' filter is applied after 'html' filter: run it again
// to update output
if (shouldRunHtmlFilter) {
tree = htmlFilter(tree, profile);
}
return tree;
};
});
},{"../assets/preferences":"assets/preferences.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","./html":"filter/html.js"}],"filter/comment.js":[function(require,module,exports){
/**
* Comment important tags (with 'id' and 'class' attributes)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var prefs = require('../assets/preferences');
var utils = require('../utils/common');
var template = require('../utils/template');
var abbrUtils = require('../utils/abbreviation');
var filterCore = require('./main');
prefs.define('filter.commentAfter',
'\n',
'A definition of comment that should be placed
after matched '
+ 'element when
comment
filter is applied. This definition '
+ 'is an ERB-style template passed to
_.template()
'
+ 'function (see Underscore.js docs for details). In template context, '
+ 'the following properties and functions are availabe:\n'
+ '
'
+ 'attr(name, before, after)
– a function that outputs'
+ 'specified attribute value concatenated with before
'
+ 'and after
strings. If attribute doesn\'t exists, the '
+ 'empty string will be returned. '
+ 'node
– current node (instance of AbbreviationNode
) '
+ 'name
– name of current tag '
+ 'padding
– current string padding, can be used '
+ 'for formatting '
+'
');
prefs.define('filter.commentBefore',
'',
'A definition of comment that should be placed
before matched '
+ 'element when
comment
filter is applied. '
+ 'For more info, read description of
filter.commentAfter
'
+ 'property');
prefs.define('filter.commentTrigger', 'id, class',
'A comma-separated list of attribute names that should exist in abbreviatoin '
+ 'where comment should be added. If you wish to add comment for '
+ 'every element, set this option to
*
');
/**
* Add comments to tag
* @param {AbbreviationNode} node
*/
function addComments(node, templateBefore, templateAfter) {
// check if comments should be added
var trigger = prefs.get('filter.commentTrigger');
if (trigger != '*') {
var shouldAdd = utils.find(trigger.split(','), function(name) {
return !!node.attribute(utils.trim(name));
});
if (!shouldAdd) {
return;
}
}
var ctx = {
node: node,
name: node.name(),
padding: node.parent ? node.parent.padding : '',
attr: function(name, before, after) {
var attr = node.attribute(name);
if (attr) {
return (before || '') + attr + (after || '');
}
return '';
}
};
var nodeBefore = templateBefore ? templateBefore(ctx) : '';
var nodeAfter = templateAfter ? templateAfter(ctx) : '';
node.start = node.start.replace(/, nodeBefore + '<');
node.end = node.end.replace(/>/, '>' + nodeAfter);
}
function process(tree, before, after) {
tree.children.forEach(function(item) {
if (abbrUtils.isBlock(item)) {
addComments(item, before, after);
}
process(item, before, after);
});
return tree;
}
return function(tree) {
var templateBefore = template(prefs.get('filter.commentBefore'));
var templateAfter = template(prefs.get('filter.commentAfter'));
return process(tree, templateBefore, templateAfter);
};
});
},{"../assets/preferences":"assets/preferences.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","../utils/template":"utils/template.js","./main":"filter/main.js"}],"filter/css.js":[function(require,module,exports){
/**
* Filter for outputting CSS and alike
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
/**
* Test if passed item is very first child in parsed tree
* @param {AbbreviationNode} item
*/
function isVeryFirstChild(item) {
return item.parent && !item.parent.parent && !item.index();
}
return function process(tree, profile, level) {
level = level || 0;
tree.children.forEach(function(item) {
if (!isVeryFirstChild(item) && profile.tag_nl !== false) {
item.start = '\n' + item.start;
}
process(item, profile, level + 1);
});
return tree;
};
});
},{}],"filter/escape.js":[function(require,module,exports){
/**
* Filter for escaping unsafe XML characters: <, >, &
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var charMap = {
'<': '<',
'>': '>',
'&': '&'
};
function escapeChars(str) {
return str.replace(/([<>&])/g, function(str, p1){
return charMap[p1];
});
}
return function process(tree) {
tree.children.forEach(function(item) {
item.start = escapeChars(item.start);
item.end = escapeChars(item.end);
item.content = escapeChars(item.content);
process(item);
});
return tree;
};
});
},{}],"filter/format.js":[function(require,module,exports){
/**
* Generic formatting filter: creates proper indentation for each tree node,
* placing "%s" placeholder where the actual output should be. You can use
* this filter to preformat tree and then replace %s placeholder to whatever you
* need. This filter should't be called directly from editor as a part
* of abbreviation.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var abbrUtils = require('../utils/abbreviation');
var prefs = require('../assets/preferences');
var resources = require('../assets/resources');
prefs.define('format.noIndentTags', 'html',
'A comma-separated list of tag names that should not get inner indentation.');
prefs.define('format.forceIndentationForTags', 'body',
'A comma-separated list of tag names that should
always get inner indentation.');
var placeholder = '%s';
/**
* Get indentation for given node
* @param {AbbreviationNode} node
* @returns {String}
*/
function getIndentation(node) {
var items = prefs.getArray('format.noIndentTags') || [];
if (~items.indexOf(node.name())) {
return '';
}
return '\t';
}
/**
* Test if passed node has block-level sibling element
* @param {AbbreviationNode} item
* @return {Boolean}
*/
function hasBlockSibling(item) {
return item.parent && abbrUtils.hasBlockChildren(item.parent);
}
/**
* Test if passed item is very first child in parsed tree
* @param {AbbreviationNode} item
*/
function isVeryFirstChild(item) {
return item.parent && !item.parent.parent && !item.index();
}
/**
* Check if a newline should be added before element
* @param {AbbreviationNode} node
* @param {OutputProfile} profile
* @return {Boolean}
*/
function shouldAddLineBreak(node, profile) {
if (profile.tag_nl === true || abbrUtils.isBlock(node))
return true;
if (!node.parent || !profile.inline_break)
return false;
// check if there are required amount of adjacent inline element
return shouldFormatInline(node.parent, profile);
}
/**
* Need to add newline because
item
has too many inline children
* @param {AbbreviationNode} node
* @param {OutputProfile} profile
*/
function shouldBreakChild(node, profile) {
// we need to test only one child element, because
// hasBlockChildren() method will do the rest
return node.children.length && shouldAddLineBreak(node.children[0], profile);
}
function shouldFormatInline(node, profile) {
var nodeCount = 0;
return !!utils.find(node.children, function(child) {
if (child.isTextNode() || !abbrUtils.isInline(child))
nodeCount = 0;
else if (abbrUtils.isInline(child))
nodeCount++;
if (nodeCount >= profile.inline_break)
return true;
});
}
function isRoot(item) {
return !item.parent;
}
/**
* Processes element with matched resource of type
snippet
* @param {AbbreviationNode} item
* @param {OutputProfile} profile
*/
function processSnippet(item, profile) {
item.start = item.end = '';
if (!isVeryFirstChild(item) && profile.tag_nl !== false && shouldAddLineBreak(item, profile)) {
// check if we’re not inside inline element
if (isRoot(item.parent) || !abbrUtils.isInline(item.parent)) {
item.start = '\n' + item.start;
}
}
return item;
}
/**
* Check if we should add line breaks inside inline element
* @param {AbbreviationNode} node
* @param {OutputProfile} profile
* @return {Boolean}
*/
function shouldBreakInsideInline(node, profile) {
var hasBlockElems = node.children.some(function(child) {
if (abbrUtils.isSnippet(child))
return false;
return !abbrUtils.isInline(child);
});
if (!hasBlockElems) {
return shouldFormatInline(node, profile);
}
return true;
}
/**
* Processes element with
tag
type
* @param {AbbreviationNode} item
* @param {OutputProfile} profile
*/
function processTag(item, profile) {
item.start = item.end = placeholder;
var isUnary = abbrUtils.isUnary(item);
var nl = '\n';
var indent = getIndentation(item);
// formatting output
if (profile.tag_nl !== false) {
var forceNl = profile.tag_nl === true && (profile.tag_nl_leaf || item.children.length);
if (!forceNl) {
var forceIndentTags = prefs.getArray('format.forceIndentationForTags') || [];
forceNl = ~forceIndentTags.indexOf(item.name());
}
// formatting block-level elements
if (!item.isTextNode()) {
if (shouldAddLineBreak(item, profile)) {
// - do not indent the very first element
// - do not indent first child of a snippet
if (!isVeryFirstChild(item) && (!abbrUtils.isSnippet(item.parent) || item.index()))
item.start = nl + item.start;
if (abbrUtils.hasBlockChildren(item) || shouldBreakChild(item, profile) || (forceNl && !isUnary))
item.end = nl + item.end;
if (abbrUtils.hasTagsInContent(item) || (forceNl && !item.children.length && !isUnary))
item.start += nl + indent;
} else if (abbrUtils.isInline(item) && hasBlockSibling(item) && !isVeryFirstChild(item)) {
item.start = nl + item.start;
} else if (abbrUtils.isInline(item) && shouldBreakInsideInline(item, profile)) {
item.end = nl + item.end;
}
item.padding = indent;
}
}
return item;
}
/**
* Processes simplified tree, making it suitable for output as HTML structure
* @param {AbbreviationNode} tree
* @param {OutputProfile} profile
* @param {Number} level Depth level
*/
return function process(tree, profile, level) {
level = level || 0;
tree.children.forEach(function(item) {
if (abbrUtils.isSnippet(item)) {
processSnippet(item, profile, level);
} else {
processTag(item, profile, level);
}
process(item, profile, level + 1);
});
return tree;
};
});
},{"../assets/preferences":"assets/preferences.js","../assets/resources":"assets/resources.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js"}],"filter/haml.js":[function(require,module,exports){
/**
* Filter for producing HAML code from abbreviation.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var abbrUtils = require('../utils/abbreviation');
var formatFilter = require('./format');
function transformClassName(className) {
return utils.trim(className).replace(/\s+/g, '.');
}
/**
* Condenses all "data-" attributes into a single entry.
* HAML allows data attributes to be ouputted as a sub-hash
* of `:data` key
* @param {Array} attrs
* @return {Array}
*/
function condenseDataAttrs(attrs) {
var out = [], data = null;
var reData = /^data-/i;
attrs.forEach(function(attr) {
if (reData.test(attr.name)) {
if (!data) {
data = [];
out.push({
name: 'data',
value: data
});
}
data.push(utils.extend({}, attr, {name: attr.name.replace(reData, '')}));
} else {
out.push(attr);
}
});
return out;
}
function stringifyAttrs(attrs, profile) {
var attrQuote = profile.attributeQuote();
return '{' + attrs.map(function(attr) {
var value = attrQuote + attr.value + attrQuote;
if (Array.isArray(attr.value)) {
value = stringifyAttrs(attr.value, profile);
} else if (attr.isBoolean) {
value = 'true';
}
return ':' + attr.name + ' => ' + value
}).join(', ') + '}';
}
/**
* Creates HAML attributes string from tag according to profile settings
* @param {AbbreviationNode} tag
* @param {Object} profile
*/
function makeAttributesString(tag, profile) {
var attrs = '';
var otherAttrs = [];
var attrQuote = profile.attributeQuote();
var cursor = profile.cursor();
tag.attributeList().forEach(function(a) {
var attrName = profile.attributeName(a.name);
switch (attrName.toLowerCase()) {
// use short notation for ID and CLASS attributes
case 'id':
attrs += '#' + (a.value || cursor);
break;
case 'class':
attrs += '.' + transformClassName(a.value || cursor);
break;
// process other attributes
default:
otherAttrs.push({
name: attrName,
value: a.value || cursor,
isBoolean: profile.isBoolean(a.name, a.value)
});
}
});
if (otherAttrs.length) {
attrs += stringifyAttrs(condenseDataAttrs(otherAttrs), profile);
}
return attrs;
}
/**
* Processes element with
tag
type
* @param {AbbreviationNode} item
* @param {OutputProfile} profile
*/
function processTag(item, profile) {
if (!item.parent)
// looks like it's root element
return item;
var attrs = makeAttributesString(item, profile);
var cursor = profile.cursor();
var isUnary = abbrUtils.isUnary(item);
var selfClosing = profile.self_closing_tag && isUnary ? '/' : '';
var start= '';
// define tag name
var tagName = '%' + profile.tagName(item.name());
if (tagName.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1)
// omit div tag
tagName = '';
item.end = '';
start = tagName + attrs + selfClosing;
if (item.content && !/^\s/.test(item.content)) {
item.content = ' ' + item.content;
}
var placeholder = '%s';
// We can't just replace placeholder with new value because
// JavaScript will treat double $ character as a single one, assuming
// we're using RegExp literal.
item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder);
if (!item.children.length && !isUnary)
item.start += cursor;
return item;
}
return function process(tree, profile, level) {
level = level || 0;
if (!level) {
tree = formatFilter(tree, '_format', profile);
}
tree.children.forEach(function(item) {
if (!abbrUtils.isSnippet(item)) {
processTag(item, profile, level);
}
process(item, profile, level + 1);
});
return tree;
};
});
},{"../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","./format":"filter/format.js"}],"filter/html.js":[function(require,module,exports){
/**
* Filter that produces HTML tree
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var abbrUtils = require('../utils/abbreviation');
var utils = require('../utils/common');
var tabStops = require('../assets/tabStops');
var formatFilter = require('./format');
/**
* Creates HTML attributes string from tag according to profile settings
* @param {AbbreviationNode} node
* @param {OutputProfile} profile
*/
function makeAttributesString(node, profile) {
var attrQuote = profile.attributeQuote();
var cursor = profile.cursor();
return node.attributeList().map(function(a) {
var isBoolean = profile.isBoolean(a.name, a.value);
var attrName = profile.attributeName(a.name);
var attrValue = isBoolean ? attrName : a.value;
if (isBoolean && profile.allowCompactBoolean()) {
return ' ' + attrName;
}
return ' ' + attrName + '=' + attrQuote + (attrValue || cursor) + attrQuote;
}).join('');
}
/**
* Processes element with
tag
type
* @param {AbbreviationNode} item
* @param {OutputProfile} profile
*/
function processTag(item, profile) {
if (!item.parent) { // looks like it's root element
return item;
}
var attrs = makeAttributesString(item, profile);
var cursor = profile.cursor();
var isUnary = abbrUtils.isUnary(item);
var start = '';
var end = '';
// define opening and closing tags
if (!item.isTextNode()) {
var tagName = profile.tagName(item.name());
if (isUnary) {
start = '<' + tagName + attrs + profile.selfClosing() + '>';
item.end = '';
} else {
start = '<' + tagName + attrs + '>';
end = '' + tagName + '>';
}
}
var placeholder = '%s';
// We can't just replace placeholder with new value because
// JavaScript will treat double $ character as a single one, assuming
// we're using RegExp literal.
item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder);
item.end = utils.replaceSubstring(item.end, end, item.end.indexOf(placeholder), placeholder);
// should we put caret placeholder after opening tag?
if (
!item.children.length
&& !isUnary
&& !~item.content.indexOf(cursor)
&& !tabStops.extract(item.content).tabstops.length
) {
item.start += cursor;
}
return item;
}
return function process(tree, profile, level) {
level = level || 0;
if (!level) {
tree = formatFilter(tree, profile, level)
}
tree.children.forEach(function(item) {
if (!abbrUtils.isSnippet(item)) {
processTag(item, profile, level);
}
process(item, profile, level + 1);
});
return tree;
};
});
},{"../assets/tabStops":"assets/tabStops.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","./format":"filter/format.js"}],"filter/jade.js":[function(require,module,exports){
/**
* Filter for producing Jade code from abbreviation.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var abbrUtils = require('../utils/abbreviation');
var formatFilter = require('./format');
var tabStops = require('../assets/tabStops');
var profile = require('../assets/profile');
var reNl = /[\n\r]/;
var reIndentedText = /^\s*\|/;
var reSpace = /^\s/;
function transformClassName(className) {
return utils.trim(className).replace(/\s+/g, '.');
}
function stringifyAttrs(attrs, profile) {
var attrQuote = profile.attributeQuote();
return '(' + attrs.map(function(attr) {
if (attr.isBoolean) {
return attr.name;
}
return attr.name + '=' + attrQuote + attr.value + attrQuote;
}).join(', ') + ')';
}
/**
* Creates HAML attributes string from tag according to profile settings
* @param {AbbreviationNode} tag
* @param {Object} profile
*/
function makeAttributesString(tag, profile) {
var attrs = '';
var otherAttrs = [];
var attrQuote = profile.attributeQuote();
var cursor = profile.cursor();
tag.attributeList().forEach(function(a) {
var attrName = profile.attributeName(a.name);
switch (attrName.toLowerCase()) {
// use short notation for ID and CLASS attributes
case 'id':
attrs += '#' + (a.value || cursor);
break;
case 'class':
attrs += '.' + transformClassName(a.value || cursor);
break;
// process other attributes
default:
otherAttrs.push({
name: attrName,
value: a.value || cursor,
isBoolean: profile.isBoolean(a.name, a.value)
});
}
});
if (otherAttrs.length) {
attrs += stringifyAttrs(otherAttrs, profile);
}
return attrs;
}
function processTagContent(item) {
if (!item.content) {
return;
}
var content = tabStops.replaceVariables(item.content, function(str, name) {
if (name === 'nl' || name === 'newline') {
return '\n';
}
return str;
});
if (reNl.test(content) && !reIndentedText.test(content)) {
// multiline content: pad it with indentation and pipe
var pad = '| ';
item.content = '\n' + pad + utils.padString(content, pad);
} else if (!reSpace.test(content)) {
item.content = ' ' + content;
}
}
/**
* Processes element with
tag
type
* @param {AbbreviationNode} item
* @param {OutputProfile} profile
*/
function processTag(item, profile) {
if (!item.parent)
// looks like it's a root (empty) element
return item;
var attrs = makeAttributesString(item, profile);
var cursor = profile.cursor();
var isUnary = abbrUtils.isUnary(item);
// define tag name
var tagName = profile.tagName(item.name());
if (tagName.toLowerCase() == 'div' && attrs && attrs.charAt(0) != '(')
// omit div tag
tagName = '';
item.end = '';
var start = tagName + attrs;
processTagContent(item);
var placeholder = '%s';
// We can't just replace placeholder with new value because
// JavaScript will treat double $ character as a single one, assuming
// we're using RegExp literal.
item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder);
if (!item.children.length && !isUnary)
item.start += cursor;
return item;
}
return function process(tree, curProfile, level) {
level = level || 0;
if (!level) {
// always format with `xml` profile since
// Jade requires all tags to be on separate lines
tree = formatFilter(tree, profile.get('xml'));
}
tree.children.forEach(function(item) {
if (!abbrUtils.isSnippet(item)) {
processTag(item, curProfile, level);
}
process(item, curProfile, level + 1);
});
return tree;
};
});
},{"../assets/profile":"assets/profile.js","../assets/tabStops":"assets/tabStops.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","./format":"filter/format.js"}],"filter/jsx.js":[function(require,module,exports){
/**
* A filter for React.js (JSX):
* ranames attributes like `class` and `for`
* for proper representation in JSX
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var attrMap = {
'class': 'className',
'for': 'htmlFor'
};
return function process(tree) {
tree.children.forEach(function(item) {
item._attributes.forEach(function(attr) {
if (attr.name in attrMap) {
attr.name = attrMap[attr.name]
}
});
process(item);
});
return tree;
};
});
},{}],"filter/main.js":[function(require,module,exports){
/**
* Module for handling filters
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var profile = require('../assets/profile');
var resources = require('../assets/resources');
/** List of registered filters */
var registeredFilters = {
html: require('./html'),
haml: require('./haml'),
jade: require('./jade'),
jsx: require('./jsx'),
slim: require('./slim'),
xsl: require('./xsl'),
css: require('./css'),
bem: require('./bem'),
c: require('./comment'),
e: require('./escape'),
s: require('./singleLine'),
t: require('./trim')
};
/** Filters that will be applied for unknown syntax */
var basicFilters = 'html';
function list(filters) {
if (!filters)
return [];
if (typeof filters === 'string') {
return filters.split(/[\|,]/g);
}
return filters;
}
return {
/**
* Register new filter
* @param {String} name Filter name
* @param {Function} fn Filter function
*/
add: function(name, fn) {
registeredFilters[name] = fn;
},
/**
* Apply filters for final output tree
* @param {AbbreviationNode} tree Output tree
* @param {Array} filters List of filters to apply. Might be a
*
String
* @param {Object} profile Output profile, defined in
profile
* module. Filters defined it profile are not used,
profile
* is passed to filter function
* @memberOf emmet.filters
* @returns {AbbreviationNode}
*/
apply: function(tree, filters, profileName) {
profileName = profile.get(profileName);
list(filters).forEach(function(filter) {
var name = utils.trim(filter.toLowerCase());
if (name && name in registeredFilters) {
tree = registeredFilters[name](tree, profileName);
}
});
return tree;
},
/**
* Composes list of filters that should be applied to a tree, based on
* passed data
* @param {String} syntax Syntax name ('html', 'css', etc.)
* @param {Object} profile Output profile
* @param {String} additionalFilters List or pipe-separated
* string of additional filters to apply
* @returns {Array}
*/
composeList: function(syntax, profileName, additionalFilters) {
profileName = profile.get(profileName);
var filters = list(profileName.filters || resources.findItem(syntax, 'filters') || basicFilters);
if (profileName.extraFilters) {
filters = filters.concat(list(profileName.extraFilters));
}
if (additionalFilters) {
filters = filters.concat(list(additionalFilters));
}
if (!filters || !filters.length) {
// looks like unknown syntax, apply basic filters
filters = list(basicFilters);
}
return filters;
},
/**
* Extracts filter list from abbreviation
* @param {String} abbr
* @returns {Array} Array with cleaned abbreviation and list of
* extracted filters
*/
extract: function(abbr) {
var filters = '';
abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1){
filters = p1;
return '';
});
return [abbr, list(filters)];
}
};
});
},{"../assets/profile":"assets/profile.js","../assets/resources":"assets/resources.js","../utils/common":"utils/common.js","./bem":"filter/bem.js","./comment":"filter/comment.js","./css":"filter/css.js","./escape":"filter/escape.js","./haml":"filter/haml.js","./html":"filter/html.js","./jade":"filter/jade.js","./jsx":"filter/jsx.js","./singleLine":"filter/singleLine.js","./slim":"filter/slim.js","./trim":"filter/trim.js","./xsl":"filter/xsl.js"}],"filter/singleLine.js":[function(require,module,exports){
/**
* Output abbreviation on a single line (i.e. no line breaks)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var abbrUtils = require('../utils/abbreviation');
var rePad = /^\s+/;
var reNl = /[\n\r]/g;
return function process(tree) {
tree.children.forEach(function(item) {
if (!abbrUtils.isSnippet(item)) {
// remove padding from item
item.start = item.start.replace(rePad, '');
item.end = item.end.replace(rePad, '');
}
// remove newlines
item.start = item.start.replace(reNl, '');
item.end = item.end.replace(reNl, '');
item.content = item.content.replace(reNl, '');
process(item);
});
return tree;
};
});
},{"../utils/abbreviation":"utils/abbreviation.js"}],"filter/slim.js":[function(require,module,exports){
/**
* Filter for producing Jade code from abbreviation.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var abbrUtils = require('../utils/abbreviation');
var formatFilter = require('./format');
var tabStops = require('../assets/tabStops');
var prefs = require('../assets/preferences');
var profile = require('../assets/profile');
var reNl = /[\n\r]/;
var reIndentedText = /^\s*\|/;
var reSpace = /^\s/;
prefs.define('slim.attributesWrapper', 'none',
'Defines how attributes will be wrapped:' +
'
' +
'none
– no wrapping; ' +
'round
— wrap attributes with round braces; ' +
'square
— wrap attributes with round braces; ' +
'curly
— wrap attributes with curly braces. ' +
'
');
function transformClassName(className) {
return utils.trim(className).replace(/\s+/g, '.');
}
function getAttrWrapper() {
var start = ' ', end = '';
switch (prefs.get('slim.attributesWrapper')) {
case 'round':
start = '(';
end = ')';
break;
case 'square':
start = '[';
end = ']';
break;
case 'curly':
start = '{';
end = '}';
break;
}
return {
start: start,
end: end
};
}
function stringifyAttrs(attrs, profile) {
var attrQuote = profile.attributeQuote();
var attrWrap = getAttrWrapper();
return attrWrap.start + attrs.map(function(attr) {
var value = attrQuote + attr.value + attrQuote;
if (attr.isBoolean) {
if (!attrWrap.end) {
value = 'true';
} else {
return attr.name;
}
}
return attr.name + '=' + value;
}).join(' ') + attrWrap.end;
}
/**
* Creates HAML attributes string from tag according to profile settings
* @param {AbbreviationNode} tag
* @param {Object} profile
*/
function makeAttributesString(tag, profile) {
var attrs = '';
var otherAttrs = [];
var attrQuote = profile.attributeQuote();
var cursor = profile.cursor();
tag.attributeList().forEach(function(a) {
var attrName = profile.attributeName(a.name);
switch (attrName.toLowerCase()) {
// use short notation for ID and CLASS attributes
case 'id':
attrs += '#' + (a.value || cursor);
break;
case 'class':
attrs += '.' + transformClassName(a.value || cursor);
break;
// process other attributes
default:
otherAttrs.push({
name: attrName,
value: a.value || cursor,
isBoolean: profile.isBoolean(a.name, a.value)
});
}
});
if (otherAttrs.length) {
attrs += stringifyAttrs(otherAttrs, profile);
}
return attrs;
}
function processTagContent(item) {
if (!item.content) {
return;
}
var content = tabStops.replaceVariables(item.content, function(str, name) {
if (name === 'nl' || name === 'newline') {
return '\n';
}
return str;
});
if (reNl.test(content) && !reIndentedText.test(content)) {
// multiline content: pad it with indentation and pipe
var pad = ' ';
item.content = '\n| ' + utils.padString(content, pad);
} else if (!reSpace.test(content)) {
item.content = ' ' + content;
}
}
/**
* Processes element with
tag
type
* @param {AbbreviationNode} item
* @param {OutputProfile} profile
*/
function processTag(item, profile) {
if (!item.parent)
// looks like it's a root (empty) element
return item;
var attrs = makeAttributesString(item, profile);
var cursor = profile.cursor();
var isUnary = abbrUtils.isUnary(item);
var selfClosing = profile.self_closing_tag && isUnary ? '/' : '';
// define tag name
var tagName = profile.tagName(item.name());
if (tagName.toLowerCase() == 'div' && attrs && '([{'.indexOf(attrs.charAt(0)) == -1)
// omit div tag
tagName = '';
item.end = '';
var start = tagName + attrs + selfClosing;
processTagContent(item);
var placeholder = '%s';
// We can't just replace placeholder with new value because
// JavaScript will treat double $ character as a single one, assuming
// we're using RegExp literal.
item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder);
if (!item.children.length && !isUnary)
item.start += cursor;
return item;
}
return function process(tree, curProfile, level) {
level = level || 0;
if (!level) {
// always format with `xml` profile since
// Slim requires all tags to be on separate lines
tree = formatFilter(tree, profile.get('xml'));
}
tree.children.forEach(function(item) {
if (!abbrUtils.isSnippet(item)) {
processTag(item, curProfile, level);
}
process(item, curProfile, level + 1);
});
return tree;
};
});
},{"../assets/preferences":"assets/preferences.js","../assets/profile":"assets/profile.js","../assets/tabStops":"assets/tabStops.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","./format":"filter/format.js"}],"filter/trim.js":[function(require,module,exports){
/**
* Trim filter: removes characters at the beginning of the text
* content that indicates lists: numbers, #, *, -, etc.
*
* Useful for wrapping lists with abbreviation.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var prefs = require('../assets/preferences');
prefs.define('filter.trimRegexp',
'[\\s|\\u00a0]*[\\d|#|\\-|\*|\\u2022]+\\.?\\s*',
'Regular expression used to remove list markers (numbers, dashes, '
+ 'bullets, etc.) in
t
(trim) filter. The trim filter '
+ 'is useful for wrapping with abbreviation lists, pased from other '
+ 'documents (for example, Word documents).');
function process(tree, re) {
tree.children.forEach(function(item) {
if (item.content) {
item.content = item.content.replace(re, '');
}
process(item, re);
});
return tree;
}
return function(tree) {
var re = new RegExp(prefs.get('filter.trimRegexp'));
return process(tree, re);
};
});
},{"../assets/preferences":"assets/preferences.js"}],"filter/xsl.js":[function(require,module,exports){
/**
* Filter for trimming "select" attributes from some tags that contains
* child elements
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var abbrUtils = require('../utils/abbreviation');
var tags = {
'xsl:variable': 1,
'xsl:with-param': 1
};
/**
* Removes "select" attribute from node
* @param {AbbreviationNode} node
*/
function trimAttribute(node) {
node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, '');
}
return function process(tree) {
tree.children.forEach(function(item) {
if (!abbrUtils.isSnippet(item)
&& (item.name() || '').toLowerCase() in tags
&& item.children.length)
trimAttribute(item);
process(item);
});
return tree;
};
});
},{"../utils/abbreviation":"utils/abbreviation.js"}],"generator/lorem.js":[function(require,module,exports){
/**
* "Lorem ipsum" text generator. Matches
lipsum(num)?
or
*
lorem(num)?
abbreviation.
* This code is based on Django's contribution:
* https://code.djangoproject.com/browser/django/trunk/django/contrib/webdesign/lorem_ipsum.py
*
* Examples to test:
*
lipsum
– generates 30 words text.
*
lipsum*6
– generates 6 paragraphs (autowrapped with <p> element) of text.
*
ol>lipsum10*5
— generates ordered list with 5 list items (autowrapped with <li> tag)
* with text of 10 words on each line.
*
span*3>lipsum20
– generates 3 paragraphs of 20-words text, each wrapped with <span> element.
* Each paragraph phrase is unique.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var prefs = require('../assets/preferences');
var langs = {
en: {
common: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipisicing', 'elit'],
words: ['exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet',
'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi',
'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi',
'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos',
'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum',
'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus',
'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus',
'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum',
'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem',
'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus',
'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente',
'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet',
'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta',
'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima',
'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim',
'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores',
'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias',
'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea',
'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt',
'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate',
'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius',
'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos',
'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore',
'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo',
'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi',
'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam',
'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique',
'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere',
'maxime', 'corrupti']
},
sp: {
common: ['mujer', 'uno', 'dolor', 'más', 'de', 'poder', 'mismo', 'si'],
words: ['ejercicio', 'preferencia', 'perspicacia', 'laboral', 'paño',
'suntuoso', 'molde', 'namibia', 'planeador', 'mirar', 'demás', 'oficinista', 'excepción',
'odio', 'consecuencia', 'casi', 'auto', 'chicharra', 'velo', 'elixir',
'ataque', 'no', 'odio', 'temporal', 'cuórum', 'dignísimo',
'facilismo', 'letra', 'nihilista', 'expedición', 'alma', 'alveolar', 'aparte',
'león', 'animal', 'como', 'paria', 'belleza', 'modo', 'natividad',
'justo', 'ataque', 'séquito', 'pillo', 'sed', 'ex', 'y', 'voluminoso',
'temporalidad', 'verdades', 'racional', 'asunción', 'incidente', 'marejada',
'placenta', 'amanecer', 'fuga', 'previsor', 'presentación', 'lejos',
'necesariamente', 'sospechoso', 'adiposidad', 'quindío', 'pócima',
'voluble', 'débito', 'sintió', 'accesorio', 'falda', 'sapiencia',
'volutas', 'queso', 'permacultura', 'laudo', 'soluciones', 'entero',
'pan', 'litro', 'tonelada', 'culpa', 'libertario', 'mosca', 'dictado',
'reincidente', 'nascimiento', 'dolor', 'escolar', 'impedimento', 'mínima',
'mayores', 'repugnante', 'dulce', 'obcecado', 'montaña', 'enigma',
'total', 'deletéreo', 'décima', 'cábala', 'fotografía', 'dolores',
'molesto', 'olvido', 'paciencia', 'resiliencia', 'voluntad', 'molestias',
'magnífico', 'distinción', 'ovni', 'marejada', 'cerro', 'torre', 'y',
'abogada', 'manantial', 'corporal', 'agua', 'crepúsculo', 'ataque', 'desierto',
'laboriosamente', 'angustia', 'afortunado', 'alma', 'encefalograma',
'materialidad', 'cosas', 'o', 'renuncia', 'error', 'menos', 'conejo',
'abadía', 'analfabeto', 'remo', 'fugacidad', 'oficio', 'en', 'almácigo', 'vos', 'pan',
'represión', 'números', 'triste', 'refugiado', 'trote', 'inventor',
'corchea', 'repelente', 'magma', 'recusado', 'patrón', 'explícito',
'paloma', 'síndrome', 'inmune', 'autoinmune', 'comodidad',
'ley', 'vietnamita', 'demonio', 'tasmania', 'repeler', 'apéndice',
'arquitecto', 'columna', 'yugo', 'computador', 'mula', 'a', 'propósito',
'fantasía', 'alias', 'rayo', 'tenedor', 'deleznable', 'ventana', 'cara',
'anemia', 'corrupto']
},
ru: {
common: ['далеко-далеко', 'за', 'словесными', 'горами', 'в стране', 'гласных', 'и согласных', 'живут', 'рыбные', 'тексты'],
words: ['вдали', 'от всех', 'они', 'буквенных', 'домах', 'на берегу', 'семантика',
'большого', 'языкового', 'океана', 'маленький', 'ручеек', 'даль',
'журчит', 'по всей', 'обеспечивает', 'ее','всеми', 'необходимыми',
'правилами', 'эта', 'парадигматическая', 'страна', 'которой', 'жаренные',
'предложения', 'залетают', 'прямо', 'рот', 'даже', 'всемогущая',
'пунктуация', 'не', 'имеет', 'власти', 'над', 'рыбными', 'текстами',
'ведущими', 'безорфографичный', 'образ', 'жизни', 'однажды', 'одна',
'маленькая', 'строчка','рыбного', 'текста', 'имени', 'lorem', 'ipsum',
'решила', 'выйти', 'большой', 'мир', 'грамматики', 'великий', 'оксмокс',
'предупреждал', 'о', 'злых', 'запятых', 'диких', 'знаках', 'вопроса',
'коварных', 'точках', 'запятой', 'но', 'текст', 'дал', 'сбить',
'себя', 'толку', 'он', 'собрал', 'семь', 'своих', 'заглавных', 'букв',
'подпоясал', 'инициал', 'за', 'пояс', 'пустился', 'дорогу',
'взобравшись', 'первую', 'вершину', 'курсивных', 'гор', 'бросил',
'последний', 'взгляд', 'назад', 'силуэт', 'своего', 'родного', 'города',
'буквоград', 'заголовок', 'деревни', 'алфавит', 'подзаголовок', 'своего',
'переулка', 'грустный', 'реторический', 'вопрос', 'скатился', 'его',
'щеке', 'продолжил', 'свой', 'путь', 'дороге', 'встретил', 'рукопись',
'она', 'предупредила', 'моей', 'все', 'переписывается', 'несколько',
'раз', 'единственное', 'что', 'меня', 'осталось', 'это', 'приставка',
'возвращайся', 'ты', 'лучше', 'свою', 'безопасную', 'страну', 'послушавшись',
'рукописи', 'наш', 'продолжил', 'свой', 'путь', 'вскоре', 'ему',
'повстречался', 'коварный', 'составитель', 'рекламных', 'текстов',
'напоивший', 'языком', 'речью', 'заманивший', 'свое', 'агенство',
'которое', 'использовало', 'снова', 'снова', 'своих', 'проектах',
'если', 'переписали', 'то', 'живет', 'там', 'до', 'сих', 'пор']
}
};
prefs.define('lorem.defaultLang', 'en',
'Default language of generated dummy text. Currently,
en
\
and
ru
are supported, but users can add their own syntaxes\
see
docs.');
prefs.define('lorem.omitCommonPart', false,
'Omit commonly used part (e.g. “Lorem ipsum dolor sit amet“) from generated text.');
/**
* Returns random integer between
from
and
to
values
* @param {Number} from
* @param {Number} to
* @returns {Number}
*/
function randint(from, to) {
return Math.round(Math.random() * (to - from) + from);
}
/**
* @param {Array} arr
* @param {Number} count
* @returns {Array}
*/
function sample(arr, count) {
var len = arr.length;
var iterations = Math.min(len, count);
var result = [];
while (result.length < iterations) {
var randIx = randint(0, len - 1);
if (!~result.indexOf(randIx)) {
result.push(randIx);
}
}
return result.map(function(ix) {
return arr[ix];
});
}
function choice(val) {
if (typeof val === 'string')
return val.charAt(randint(0, val.length - 1));
return val[randint(0, val.length - 1)];
}
function sentence(words, end) {
if (words.length) {
words[0] = words[0].charAt(0).toUpperCase() + words[0].substring(1);
}
return words.join(' ') + (end || choice('?!...')); // more dots than question marks
}
/**
* Insert commas at randomly selected words. This function modifies values
* inside
words
array
* @param {Array} words
*/
function insertCommas(words) {
var len = words.length;
if (len < 2) {
return;
}
var totalCommas = 0;
if (len > 3 && len <= 6) {
totalCommas = randint(0, 1);
} else if (len > 6 && len <= 12) {
totalCommas = randint(0, 2);
} else {
totalCommas = randint(1, 4);
}
for (var i = 0, pos, word; i < totalCommas; i++) {
pos = randint(0, words.length - 2);
word = words[pos];
if (word.charAt(word.length - 1) !== ',') {
words[pos] += ',';
}
}
}
/**
* Generate a paragraph of "Lorem ipsum" text
* @param {Number} wordCount Words count in paragraph
* @param {Boolean} startWithCommon Should paragraph start with common
* "lorem ipsum" sentence.
* @returns {String}
*/
function paragraph(lang, wordCount, startWithCommon) {
var data = langs[lang];
if (!data) {
return '';
}
var result = [];
var totalWords = 0;
var words;
wordCount = parseInt(wordCount, 10);
if (startWithCommon && data.common) {
words = data.common.slice(0, wordCount);
if (words.length > 5) {
words[4] += ',';
}
totalWords += words.length;
result.push(sentence(words, '.'));
}
while (totalWords < wordCount) {
words = sample(data.words, Math.min(randint(2, 30), wordCount - totalWords));
totalWords += words.length;
insertCommas(words);
result.push(sentence(words));
}
return result.join(' ');
}
return {
/**
* Adds new language words for Lorem Ipsum generator
* @param {String} lang Two-letter lang definition
* @param {Object} data Words for language. Maight be either a space-separated
* list of words (String), Array of words or object with
text
and
*
common
properties
*/
addLang: function(lang, data) {
if (typeof data === 'string') {
data = {
words: data.split(' ').filter(function(item) {
return !!item;
})
};
} else if (Array.isArray(data)) {
data = {words: data};
}
langs[lang] = data;
},
preprocessor: function(tree) {
var re = /^(?:lorem|lipsum)([a-z]{2})?(\d*)$/i, match;
var allowCommon = !prefs.get('lorem.omitCommonPart');
/** @param {AbbreviationNode} node */
tree.findAll(function(node) {
if (node._name && (match = node._name.match(re))) {
var wordCound = match[2] || 30;
var lang = match[1] || prefs.get('lorem.defaultLang') || 'en';
// force node name resolving if node should be repeated
// or contains attributes. In this case, node should be outputed
// as tag, otherwise as text-only node
node._name = '';
node.data('forceNameResolving', node.isRepeating() || node.attributeList().length);
node.data('pasteOverwrites', true);
node.data('paste', function(i) {
return paragraph(lang, wordCound, !i && allowCommon);
});
}
});
}
};
});
},{"../assets/preferences":"assets/preferences.js"}],"parser/abbreviation.js":[function(require,module,exports){
/**
* Emmet abbreviation parser.
* Takes string abbreviation and recursively parses it into a tree. The parsed
* tree can be transformed into a string representation with
*
toString()
method. Note that string representation is defined
* by custom processors (called
filters), not by abbreviation parser
* itself.
*
* This module can be extended with custom pre-/post-processors to shape-up
* final tree or its representation. Actually, many features of abbreviation
* engine are defined in other modules as tree processors
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var tabStops = require('../assets/tabStops');
var profile = require('../assets/profile');
var filters = require('../filter/main');
var utils = require('../utils/common');
var abbreviationUtils = require('../utils/abbreviation');
var stringStream = require('../assets/stringStream');
// pre- and post-processorcs
var lorem = require('../generator/lorem');
var procPastedContent = require('./processor/pastedContent');
var procTagName = require('./processor/tagName');
var procResourceMatcher = require('./processor/resourceMatcher');
var procAttributes = require('./processor/attributes');
var procHref = require('./processor/href');
var reValidName = /^[\w\-\$\:@\!%]+\+?$/i;
var reWord = /[\w\-:\$@]/;
var DEFAULT_ATTR_NAME = '%default';
var pairs = {
'[': ']',
'(': ')',
'{': '}'
};
var spliceFn = Array.prototype.splice;
var preprocessors = [];
var postprocessors = [];
var outputProcessors = [];
/**
* @type AbbreviationNode
*/
function AbbreviationNode(parent) {
/** @type AbbreviationNode */
this.parent = null;
this.children = [];
this._attributes = [];
/** @type String Raw abbreviation for current node */
this.abbreviation = '';
this.counter = 1;
this._name = null;
this._text = '';
this.repeatCount = 1;
this.hasImplicitRepeat = false;
/** Custom data dictionary */
this._data = {};
// output properties
this.start = '';
this.end = '';
this.content = '';
this.padding = '';
}
AbbreviationNode.prototype = {
/**
* Adds passed node as child or creates new child
* @param {AbbreviationNode} child
* @param {Number} position Index in children array where child should
* be inserted
* @return {AbbreviationNode}
*/
addChild: function(child, position) {
child = child || new AbbreviationNode();
child.parent = this;
if (typeof position === 'undefined') {
this.children.push(child);
} else {
this.children.splice(position, 0, child);
}
return child;
},
/**
* Creates a deep copy of current node
* @returns {AbbreviationNode}
*/
clone: function() {
var node = new AbbreviationNode();
var attrs = ['abbreviation', 'counter', '_name', '_text', 'repeatCount', 'hasImplicitRepeat', 'start', 'end', 'content', 'padding'];
attrs.forEach(function(a) {
node[a] = this[a];
}, this);
// clone attributes
node._attributes = this._attributes.map(function(attr) {
return utils.extend({}, attr);
});
node._data = utils.extend({}, this._data);
// clone children
node.children = this.children.map(function(child) {
child = child.clone();
child.parent = node;
return child;
});
return node;
},
/**
* Removes current node from parent‘s child list
* @returns {AbbreviationNode} Current node itself
*/
remove: function() {
if (this.parent) {
var ix = this.parent.children.indexOf(this);
if (~ix) {
this.parent.children.splice(ix, 1);
}
}
return this;
},
/**
* Replaces current node in parent‘s children list with passed nodes
* @param {AbbreviationNode} node Replacement node or array of nodes
*/
replace: function() {
var parent = this.parent;
var ix = parent.children.indexOf(this);
var items = utils.flatten(arguments);
spliceFn.apply(parent.children, [ix, 1].concat(items));
// update parent
items.forEach(function(item) {
item.parent = parent;
});
},
/**
* Recursively sets
property
to
value
of current
* node and its children
* @param {String} name Property to update
* @param {Object} value New property value
*/
updateProperty: function(name, value) {
this[name] = value;
this.children.forEach(function(child) {
child.updateProperty(name, value);
});
return this;
},
/**
* Finds first child node that matches truth test for passed
*
fn
function
* @param {Function} fn
* @returns {AbbreviationNode}
*/
find: function(fn) {
return this.findAll(fn, {amount: 1})[0];
},
/**
* Finds all child nodes that matches truth test for passed
*
fn
function
* @param {Function} fn
* @returns {Array}
*/
findAll: function(fn, state) {
state = utils.extend({amount: 0, found: 0}, state || {});
if (typeof fn !== 'function') {
var elemName = fn.toLowerCase();
fn = function(item) {return item.name().toLowerCase() == elemName;};
}
var result = [];
this.children.forEach(function(child) {
if (fn(child)) {
result.push(child);
state.found++;
if (state.amount && state.found >= state.amount) {
return;
}
}
result = result.concat(child.findAll(fn));
});
return result.filter(function(item) {
return !!item;
});
},
/**
* Sets/gets custom data
* @param {String} name
* @param {Object} value
* @returns {Object}
*/
data: function(name, value) {
if (arguments.length == 2) {
this._data[name] = value;
}
return this._data[name];
},
/**
* Returns name of current node
* @returns {String}
*/
name: function() {
return this._name;
},
/**
* Returns list of attributes for current node
* @returns {Array}
*/
attributeList: function() {
return optimizeAttributes(this._attributes.slice(0));
},
/**
* Returns or sets attribute value
* @param {String} name Attribute name
* @param {String} value New attribute value. `Null` value
* will remove attribute
* @returns {String}
*/
attribute: function(name, value) {
if (arguments.length == 2) {
if (value === null) {
// remove attribute
var vals = this._attributes.filter(function(attr) {
return attr.name === name;
});
var that = this;
vals.forEach(function(attr) {
var ix = that._attributes.indexOf(attr);
if (~ix) {
that._attributes.splice(ix, 1);
}
});
return;
}
// modify attribute
var attrNames = this._attributes.map(function(attr) {
return attr.name;
});
var ix = attrNames.indexOf(name.toLowerCase());
if (~ix) {
this._attributes[ix].value = value;
} else {
this._attributes.push({
name: name,
value: value
});
}
}
return (utils.find(this.attributeList(), function(attr) {
return attr.name == name;
}) || {}).value;
},
/**
* Returns index of current node in parent‘s children list
* @returns {Number}
*/
index: function() {
return this.parent ? this.parent.children.indexOf(this) : -1;
},
/**
* Sets how many times current element should be repeated
* @private
*/
_setRepeat: function(count) {
if (count) {
this.repeatCount = parseInt(count, 10) || 1;
} else {
this.hasImplicitRepeat = true;
}
},
/**
* Sets abbreviation that belongs to current node
* @param {String} abbr
*/
setAbbreviation: function(abbr) {
abbr = abbr || '';
var that = this;
// find multiplier
abbr = abbr.replace(/\*(\d+)?$/, function(str, repeatCount) {
that._setRepeat(repeatCount);
return '';
});
this.abbreviation = abbr;
var abbrText = extractText(abbr);
if (abbrText) {
abbr = abbrText.element;
this.content = this._text = abbrText.text;
}
var abbrAttrs = parseAttributes(abbr);
if (abbrAttrs) {
abbr = abbrAttrs.element;
this._attributes = abbrAttrs.attributes;
}
this._name = abbr;
// validate name
if (this._name && !reValidName.test(this._name)) {
throw new Error('Invalid abbreviation');
}
},
/**
* Returns string representation of current node
* @return {String}
*/
valueOf: function() {
var start = this.start;
var end = this.end;
var content = this.content;
// apply output processors
var node = this;
outputProcessors.forEach(function(fn) {
start = fn(start, node, 'start');
content = fn(content, node, 'content');
end = fn(end, node, 'end');
});
var innerContent = this.children.map(function(child) {
return child.valueOf();
}).join('');
content = abbreviationUtils.insertChildContent(content, innerContent, {
keepVariable: false
});
return start + utils.padString(content, this.padding) + end;
},
toString: function() {
return this.valueOf();
},
/**
* Check if current node contains children with empty
expr
* property
* @return {Boolean}
*/
hasEmptyChildren: function() {
return !!utils.find(this.children, function(child) {
return child.isEmpty();
});
},
/**
* Check if current node has implied name that should be resolved
* @returns {Boolean}
*/
hasImplicitName: function() {
return !this._name && !this.isTextNode();
},
/**
* Indicates that current element is a grouping one, e.g. has no
* representation but serves as a container for other nodes
* @returns {Boolean}
*/
isGroup: function() {
return !this.abbreviation;
},
/**
* Indicates empty node (i.e. without abbreviation). It may be a
* grouping node and should not be outputted
* @return {Boolean}
*/
isEmpty: function() {
return !this.abbreviation && !this.children.length;
},
/**
* Indicates that current node should be repeated
* @returns {Boolean}
*/
isRepeating: function() {
return this.repeatCount > 1 || this.hasImplicitRepeat;
},
/**
* Check if current node is a text-only node
* @return {Boolean}
*/
isTextNode: function() {
return !this.name() && !this.attributeList().length;
},
/**
* Indicates whether this node may be used to build elements or snippets
* @returns {Boolean}
*/
isElement: function() {
return !this.isEmpty() && !this.isTextNode();
},
/**
* Returns latest and deepest child of current tree
* @returns {AbbreviationNode}
*/
deepestChild: function() {
if (!this.children.length)
return null;
var deepestChild = this;
while (deepestChild.children.length) {
deepestChild = deepestChild.children[deepestChild.children.length - 1];
}
return deepestChild;
}
};
/**
* Returns stripped string: a string without first and last character.
* Used for “unquoting” strings
* @param {String} str
* @returns {String}
*/
function stripped(str) {
return str.substring(1, str.length - 1);
}
function consumeQuotedValue(stream, quote) {
var ch;
while ((ch = stream.next())) {
if (ch === quote)
return true;
if (ch == '\\')
continue;
}
return false;
}
/**
* Parses abbreviation into a tree
* @param {String} abbr
* @returns {AbbreviationNode}
*/
function parseAbbreviation(abbr) {
abbr = utils.trim(abbr);
var root = new AbbreviationNode();
var context = root.addChild(), ch;
/** @type StringStream */
var stream = stringStream.create(abbr);
var loopProtector = 1000, multiplier;
var addChild = function(child) {
context.addChild(child);
};
var consumeAbbr = function() {
stream.start = stream.pos;
stream.eatWhile(function(c) {
if (c == '[' || c == '{') {
if (stream.skipToPair(c, pairs[c])) {
stream.backUp(1);
return true;
}
throw new Error('Invalid abbreviation: mo matching "' + pairs[c] + '" found for character at ' + stream.pos);
}
if (c == '+') {
// let's see if this is an expando marker
stream.next();
var isMarker = stream.eol() || ~'+>^*'.indexOf(stream.peek());
stream.backUp(1);
return isMarker;
}
return c != '(' && isAllowedChar(c);
});
};
while (!stream.eol() && --loopProtector > 0) {
ch = stream.peek();
switch (ch) {
case '(': // abbreviation group
stream.start = stream.pos;
if (stream.skipToPair('(', ')')) {
var inner = parseAbbreviation(stripped(stream.current()));
if ((multiplier = stream.match(/^\*(\d+)?/, true))) {
context._setRepeat(multiplier[1]);
}
inner.children.forEach(addChild);
} else {
throw new Error('Invalid abbreviation: mo matching ")" found for character at ' + stream.pos);
}
break;
case '>': // child operator
context = context.addChild();
stream.next();
break;
case '+': // sibling operator
context = context.parent.addChild();
stream.next();
break;
case '^': // climb up operator
var parent = context.parent || context;
context = (parent.parent || parent).addChild();
stream.next();
break;
default: // consume abbreviation
consumeAbbr();
context.setAbbreviation(stream.current());
stream.start = stream.pos;
}
}
if (loopProtector < 1) {
throw new Error('Endless loop detected');
}
return root;
}
/**
* Splits attribute set into a list of attributes string
* @param {String} attrSet
* @return {Array}
*/
function splitAttributes(attrSet) {
attrSet = utils.trim(attrSet);
var parts = [];
// split attribute set by spaces
var stream = stringStream(attrSet), ch;
while ((ch = stream.next())) {
if (ch == ' ') {
parts.push(utils.trim(stream.current()));
// skip spaces
while (stream.peek() == ' ') {
stream.next();
}
stream.start = stream.pos;
} else if (ch == '"' || ch == "'") {
// skip values in strings
if (!stream.skipString(ch)) {
throw new Error('Invalid attribute set');
}
}
}
parts.push(utils.trim(stream.current()));
return parts;
}
/**
* Removes opening and closing quotes from given string
* @param {String} str
* @return {String}
*/
function unquote(str) {
var ch = str.charAt(0);
if (ch == '"' || ch == "'") {
str = str.substr(1);
var last = str.charAt(str.length - 1);
if (last === ch) {
str = str.substr(0, str.length - 1);
}
}
return str;
}
/**
* Extract attributes and their values from attribute set:
*
[attr col=3 title="Quoted string"]
(without square braces)
* @param {String} attrSet
* @returns {Array}
*/
function extractAttributes(attrSet) {
var reAttrName = /^[\w\-:\$@]+\.?$/;
return splitAttributes(attrSet).map(function(attr) {
// attribute name: [attr]
if (reAttrName.test(attr)) {
var value = '';
if (attr.charAt(attr.length - 1) == '.') {
// a boolean attribute
attr = attr.substr(0, attr.length - 1);
value = attr;
}
return {
name: attr,
value: value
};
}
// attribute with value: [name=val], [name="val"]
if (~attr.indexOf('=')) {
var parts = attr.split('=');
return {
name: parts.shift(),
value: unquote(parts.join('='))
};
}
// looks like it’s implied attribute
return {
name: DEFAULT_ATTR_NAME,
value: unquote(attr)
};
});
}
/**
* Parses tag attributes extracted from abbreviation. If attributes found,
* returns object with
element
and
attributes
* properties
* @param {String} abbr
* @returns {Object} Returns
null
if no attributes found in
* abbreviation
*/
function parseAttributes(abbr) {
/*
* Example of incoming data:
* #header
* .some.data
* .some.data#header
* [attr]
* #item[attr=Hello other="World"].class
*/
var result = [];
var attrMap = {'#': 'id', '.': 'class'};
var nameEnd = null;
/** @type StringStream */
var stream = stringStream.create(abbr);
while (!stream.eol()) {
switch (stream.peek()) {
case '#': // id
case '.': // class
if (nameEnd === null)
nameEnd = stream.pos;
var attrName = attrMap[stream.peek()];
stream.next();
stream.start = stream.pos;
stream.eatWhile(reWord);
result.push({
name: attrName,
value: stream.current()
});
break;
case '[': //begin attribute set
if (nameEnd === null)
nameEnd = stream.pos;
stream.start = stream.pos;
if (!stream.skipToPair('[', ']')) {
throw new Error('Invalid attribute set definition');
}
result = result.concat(
extractAttributes(stripped(stream.current()))
);
break;
default:
stream.next();
}
}
if (!result.length)
return null;
return {
element: abbr.substring(0, nameEnd),
attributes: optimizeAttributes(result)
};
}
/**
* Optimize attribute set: remove duplicates and merge class attributes
* @param attrs
*/
function optimizeAttributes(attrs) {
// clone all attributes to make sure that original objects are
// not modified
attrs = attrs.map(function(attr) {
return utils.clone(attr);
});
var lookup = {};
return attrs.filter(function(attr) {
if (!(attr.name in lookup)) {
return lookup[attr.name] = attr;
}
var la = lookup[attr.name];
if (attr.name.toLowerCase() == 'class') {
la.value += (la.value.length ? ' ' : '') + attr.value;
} else {
la.value = attr.value;
la.isImplied = !!attr.isImplied;
}
return false;
});
}
/**
* Extract text data from abbreviation: if
a{hello}
abbreviation
* is passed, returns object
{element: 'a', text: 'hello'}
.
* If nothing found, returns
null
* @param {String} abbr
*
*/
function extractText(abbr) {
if (!~abbr.indexOf('{'))
return null;
/** @type StringStream */
var stream = stringStream.create(abbr);
while (!stream.eol()) {
switch (stream.peek()) {
case '[':
case '(':
stream.skipToPair(stream.peek(), pairs[stream.peek()]); break;
case '{':
stream.start = stream.pos;
stream.skipToPair('{', '}');
return {
element: abbr.substring(0, stream.start),
text: stripped(stream.current())
};
default:
stream.next();
}
}
}
/**
* “Un-rolls“ contents of current node: recursively replaces all repeating
* children with their repeated clones
* @param {AbbreviationNode} node
* @returns {AbbreviationNode}
*/
function unroll(node) {
for (var i = node.children.length - 1, j, child, maxCount; i >= 0; i--) {
child = node.children[i];
if (child.isRepeating()) {
maxCount = j = child.repeatCount;
child.repeatCount = 1;
child.updateProperty('counter', 1);
child.updateProperty('maxCount', maxCount);
while (--j > 0) {
child.parent.addChild(child.clone(), i + 1)
.updateProperty('counter', j + 1)
.updateProperty('maxCount', maxCount);
}
}
}
// to keep proper 'counter' property, we need to walk
// on children once again
node.children.forEach(unroll);
return node;
}
/**
* Optimizes tree node: replaces empty nodes with their children
* @param {AbbreviationNode} node
* @return {AbbreviationNode}
*/
function squash(node) {
for (var i = node.children.length - 1; i >= 0; i--) {
/** @type AbbreviationNode */
var n = node.children[i];
if (n.isGroup()) {
n.replace(squash(n).children);
} else if (n.isEmpty()) {
n.remove();
}
}
node.children.forEach(squash);
return node;
}
function isAllowedChar(ch) {
var charCode = ch.charCodeAt(0);
var specialChars = '#.*:$-_!@|%';
return (charCode > 64 && charCode < 91) // uppercase letter
|| (charCode > 96 && charCode < 123) // lowercase letter
|| (charCode > 47 && charCode < 58) // number
|| specialChars.indexOf(ch) != -1; // special character
}
// XXX add counter replacer function as output processor
outputProcessors.push(function(text, node) {
return utils.replaceCounter(text, node.counter, node.maxCount);
});
// XXX add tabstop updater
outputProcessors.push(tabStops.abbrOutputProcessor.bind(tabStops));
// include default pre- and postprocessors
[lorem, procResourceMatcher, procAttributes, procPastedContent, procTagName, procHref].forEach(function(mod) {
if (mod.preprocessor) {
preprocessors.push(mod.preprocessor.bind(mod));
}
if (mod.postprocessor) {
postprocessors.push(mod.postprocessor.bind(mod));
}
});
return {
DEFAULT_ATTR_NAME: DEFAULT_ATTR_NAME,
/**
* Parses abbreviation into tree with respect of groups,
* text nodes and attributes. Each node of the tree is a single
* abbreviation. Tree represents actual structure of the outputted
* result
* @memberOf abbreviationParser
* @param {String} abbr Abbreviation to parse
* @param {Object} options Additional options for parser and processors
*
* @return {AbbreviationNode}
*/
parse: function(abbr, options) {
options = options || {};
var tree = parseAbbreviation(abbr);
var that = this;
if (options.contextNode) {
// add info about context node –
// a parent XHTML node in editor inside which abbreviation is
// expanded
tree._name = options.contextNode.name;
var attrLookup = {};
tree._attributes.forEach(function(attr) {
attrLookup[attr.name] = attr;
});
options.contextNode.attributes.forEach(function(attr) {
if (attr.name in attrLookup) {
attrLookup[attr.name].value = attr.value;
} else {
attr = utils.clone(attr);
tree._attributes.push(attr);
attrLookup[attr.name] = attr;
}
});
}
// apply preprocessors
preprocessors.forEach(function(fn) {
fn(tree, options, that);
});
if ('counter' in options) {
tree.updateProperty('counter', options.counter);
}
tree = squash(unroll(tree));
// apply postprocessors
postprocessors.forEach(function(fn) {
fn(tree, options, that);
});
return tree;
},
/**
* Expands given abbreviation into a formatted code structure.
* This is the main method that is used for expanding abbreviation
* @param {String} abbr Abbreviation to expand
* @param {Options} options Additional options for abbreviation
* expanding and transformation: `syntax`, `profile`, `contextNode` etc.
* @return {String}
*/
expand: function(abbr, options) {
if (!abbr) return '';
if (typeof options == 'string') {
throw new Error('Deprecated use of `expand` method: `options` must be object');
}
options = options || {};
if (!options.syntax) {
options.syntax = utils.defaultSyntax();
}
var p = profile.get(options.profile, options.syntax);
tabStops.resetTabstopIndex();
var data = filters.extract(abbr);
var outputTree = this.parse(data[0], options);
var filtersList = filters.composeList(options.syntax, p, data[1]);
filters.apply(outputTree, filtersList, p);
return outputTree.valueOf();
},
AbbreviationNode: AbbreviationNode,
/**
* Add new abbreviation preprocessor.
Preprocessor is a function
* that applies to a parsed abbreviation tree right after it get parsed.
* The passed tree is in unoptimized state.
* @param {Function} fn Preprocessor function. This function receives
* two arguments: parsed abbreviation tree (
AbbreviationNode
)
* and
options
hash that was passed to
parse
* method
*/
addPreprocessor: function(fn) {
if (!~preprocessors.indexOf(fn)) {
preprocessors.push(fn);
}
},
/**
* Removes registered preprocessor
*/
removeFilter: function(fn) {
var ix = preprocessors.indexOf(fn);
if (~ix) {
preprocessors.splice(ix, 1);
}
},
/**
* Adds new abbreviation postprocessor.
Postprocessor is a
* functinon that applies to
optimized parsed abbreviation tree
* right before it returns from
parse()
method
* @param {Function} fn Postprocessor function. This function receives
* two arguments: parsed abbreviation tree (
AbbreviationNode
)
* and
options
hash that was passed to
parse
* method
*/
addPostprocessor: function(fn) {
if (!~postprocessors.indexOf(fn)) {
postprocessors.push(fn);
}
},
/**
* Removes registered postprocessor function
*/
removePostprocessor: function(fn) {
var ix = postprocessors.indexOf(fn);
if (~ix) {
postprocessors.splice(ix, 1);
}
},
/**
* Registers output postprocessor.
Output processor is a
* function that applies to output part (
start
,
*
end
and
content
) when
*
AbbreviationNode.toString()
method is called
*/
addOutputProcessor: function(fn) {
if (!~outputProcessors.indexOf(fn)) {
outputProcessors.push(fn);
}
},
/**
* Removes registered output processor
*/
removeOutputProcessor: function(fn) {
var ix = outputProcessors.indexOf(fn);
if (~ix) {
outputProcessors.splice(ix, 1);
}
},
/**
* Check if passed symbol is valid symbol for abbreviation expression
* @param {String} ch
* @return {Boolean}
*/
isAllowedChar: function(ch) {
ch = String(ch); // convert Java object to JS
return isAllowedChar(ch) || ~'>+^[](){}'.indexOf(ch);
}
};
});
},{"../assets/profile":"assets/profile.js","../assets/stringStream":"assets/stringStream.js","../assets/tabStops":"assets/tabStops.js","../filter/main":"filter/main.js","../generator/lorem":"generator/lorem.js","../utils/abbreviation":"utils/abbreviation.js","../utils/common":"utils/common.js","./processor/attributes":"parser/processor/attributes.js","./processor/href":"parser/processor/href.js","./processor/pastedContent":"parser/processor/pastedContent.js","./processor/resourceMatcher":"parser/processor/resourceMatcher.js","./processor/tagName":"parser/processor/tagName.js"}],"parser/css.js":[function(require,module,exports){
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var session = {tokens: null};
// walks around the source
var walker = {
init: function (source) {
// this.source = source.replace(/\r\n?/g, '\n');
this.source = source;
this.ch = '';
this.chnum = -1;
// advance
this.nextChar();
},
nextChar: function () {
return this.ch = this.source.charAt(++this.chnum);
},
peek: function() {
return this.source.charAt(this.chnum + 1);
}
};
// utility helpers
function isNameChar(c, cc) {
cc = cc || c.charCodeAt(0);
return (
(cc >= 97 && cc <= 122 /* a-z */) ||
(cc >= 65 && cc <= 90 /* A-Z */) ||
/*
Experimental: include cyrillic ranges
since some letters, similar to latin ones, can
accidentally appear in CSS tokens
*/
(cc >= 1024 && cc <= 1279) ||
c === '&' || /* selector placeholder (LESS, SCSS) */
c === '_' ||
c === '<' || /* comparisons (LESS, SCSS) */
c === '>' ||
c === '=' ||
c === '-'
);
}
function isDigit(c, cc) {
cc = cc || c.charCodeAt(0);
return (cc >= 48 && cc <= 57);
}
var isOp = (function () {
var opsa = "{}[]()+*=.,;:>~|\\%$#@^!".split(''),
opsmatcha = "*^|$~".split(''),
ops = {},
opsmatch = {},
i = 0;
for (; i < opsa.length; i += 1) {
ops[opsa[i]] = true;
}
for (i = 0; i < opsmatcha.length; i += 1) {
opsmatch[opsmatcha[i]] = true;
}
return function (ch, matchattr) {
if (matchattr) {
return ch in opsmatch;
}
return ch in ops;
};
}());
// creates token objects and pushes them to a list
function tokener(value, type) {
session.tokens.push({
value: value,
type: type || value,
start: null,
end: null
});
}
function getPosInfo(w) {
var errPos = w.chnum;
var source = w.source.replace(/\r\n?/g, '\n');
var part = w.source.substring(0, errPos + 1).replace(/\r\n?/g, '\n');
var lines = part.split('\n');
var ch = (lines[lines.length - 1] || '').length;
var fullLine = source.split('\n')[lines.length - 1] || '';
var chunkSize = 100;
var offset = Math.max(0, ch - chunkSize);
var formattedLine = fullLine.substr(offset, chunkSize * 2) + '\n';
for (var i = 0; i < ch - offset - 1; i++) {
formattedLine += '-';
}
formattedLine += '^';
return {
line: lines.length,
ch: ch,
text: fullLine,
hint: formattedLine
};
}
function raiseError(message) {
var err = error(message);
var errObj = new Error(err.message, '', err.line);
errObj.line = err.line;
errObj.ch = err.ch;
errObj.name = err.name;
errObj.hint = err.hint;
throw errObj;
}
// oops
function error(m) {
var w = walker;
var info = getPosInfo(walker);
var tokens = session.tokens;
session.tokens = null;
var message = 'CSS parsing error at line ' + info.line + ', char ' + info.ch + ': ' + m;
message += '\n' + info.hint;
return {
name: "ParseError",
message: message,
hint: info.hint,
line: info.line,
ch: info.ch
};
}
// token handlers follow for:
// white space, comment, string, identifier, number, operator
function white() {
var c = walker.ch,
token = '';
while (c === " " || c === "\t") {
token += c;
c = walker.nextChar();
}
tokener(token, 'white');
}
function comment() {
var w = walker,
c = w.ch,
token = c,
cnext;
cnext = w.nextChar();
if (cnext === '/') {
// inline comment in SCSS and LESS
while (c && !(cnext === "\n" || cnext === "\r")) {
token += cnext;
c = cnext;
cnext = w.nextChar();
}
} else if (cnext === '*') {
// multiline CSS commment
while (c && !(c === "*" && cnext === "/")) {
token += cnext;
c = cnext;
cnext = w.nextChar();
}
} else {
// oops, not a comment, just a /
return tokener(token, token);
}
token += cnext;
w.nextChar();
tokener(token, 'comment');
}
function eatString() {
var w = walker,
c = w.ch,
q = c,
token = c,
cnext;
c = w.nextChar();
while (c !== q) {
if (c === '\n') {
cnext = w.nextChar();
if (cnext === "\\") {
token += c + cnext;
} else {
// end of line with no \ escape = bad
raiseError("Unterminated string");
}
} else {
if (c === "\\") {
token += c + w.nextChar();
} else {
token += c;
}
}
c = w.nextChar();
}
token += c;
return token;
}
function str() {
var token = eatString();
walker.nextChar();
tokener(token, 'string');
}
function brace() {
var w = walker,
c = w.ch,
depth = 1,
token = c,
stop = false;
c = w.nextChar();
while (c && !stop) {
if (c === '(') {
depth++;
} else if (c === ')') {
depth--;
if (!depth) {
stop = true;
}
} else if (c === '"' || c === "'") {
c = eatString();
} else if (c === '') {
raiseError("Unterminated brace");
}
token += c;
c = w.nextChar();
}
tokener(token, 'brace');
}
function identifier(pre) {
var c = walker.ch;
var token = pre ? pre + c : c;
c = walker.nextChar();
var cc = c.charCodeAt(0);
while (isNameChar(c, cc) || isDigit(c, cc)) {
token += c;
c = walker.nextChar();
cc = c.charCodeAt(0);
}
tokener(token, 'identifier');
}
function num() {
var w = walker,
c = w.ch,
token = c,
point = token === '.',
nondigit;
c = w.nextChar();
nondigit = !isDigit(c);
// .2px or .classname?
if (point && nondigit) {
// meh, NaN, could be a class name, so it's an operator for now
return tokener(token, '.');
}
// -2px or -moz-something
if (token === '-' && nondigit) {
return identifier('-');
}
while (c !== '' && (isDigit(c) || (!point && c === '.'))) { // not end of source && digit or first instance of .
if (c === '.') {
point = true;
}
token += c;
c = w.nextChar();
}
tokener(token, 'number');
}
function op() {
var w = walker,
c = w.ch,
token = c,
next = w.nextChar();
if (next === "=" && isOp(token, true)) {
token += next;
tokener(token, 'match');
w.nextChar();
return;
}
tokener(token, token);
}
// call the appropriate handler based on the first character in a token suspect
function tokenize() {
var ch = walker.ch;
if (ch === " " || ch === "\t") {
return white();
}
if (ch === '/') {
return comment();
}
if (ch === '"' || ch === "'") {
return str();
}
if (ch === '(') {
return brace();
}
if (ch === '-' || ch === '.' || isDigit(ch)) { // tricky - char: minus (-1px) or dash (-moz-stuff)
return num();
}
if (isNameChar(ch)) {
return identifier();
}
if (isOp(ch)) {
return op();
}
if (ch === '\r') {
if (walker.peek() === '\n') {
ch += walker.nextChar();
}
tokener(ch, 'line');
walker.nextChar();
return;
}
if (ch === '\n') {
tokener(ch, 'line');
walker.nextChar();
return;
}
raiseError("Unrecognized character '" + ch + "'");
}
return {
/**
* Sprits given source into tokens
* @param {String} source
* @returns {Array}
*/
lex: function (source) {
walker.init(source);
session.tokens = [];
// for empty source, return single space token
if (!source) {
session.tokens.push(this.white());
} else {
while (walker.ch !== '') {
tokenize();
}
}
var tokens = session.tokens;
session.tokens = null;
return tokens;
},
/**
* Tokenizes CSS source. It's like `lex()` method,
* but also stores proper token indexes in source,
* so it's a bit slower
* @param {String} source
* @returns {Array}
*/
parse: function(source) {
// transform tokens
var tokens = this.lex(source), pos = 0, token;
for (var i = 0, il = tokens.length; i < il; i++) {
token = tokens[i];
token.start = pos;
token.end = (pos += token.value.length);
}
return tokens;
},
white: function() {
return {
value: '',
type: 'white',
start: 0,
end: 0
};
},
toSource: function(toks) {
var i = 0, max = toks.length, src = '';
for (; i < max; i++) {
src += toks[i].value;
}
return src;
}
};
});
},{}],"parser/processor/attributes.js":[function(require,module,exports){
/**
* Resolves node attribute names: moves `default` attribute value
* from stub to real attribute.
*
* This resolver should be applied *after* resource matcher
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../../utils/common');
var findDefault = function(attr) {
return attr.isDefault;
};
var findImplied = function(attr) {
return attr.isImplied;
};
var findEmpty = function(attr) {
return !attr.value;
};
function resolveDefaultAttrs(node, parser) {
node.children.forEach(function(item) {
var attrList = item.attributeList();
var defaultAttrValue = item.attribute(parser.DEFAULT_ATTR_NAME);
if (typeof defaultAttrValue !== 'undefined') {
// remove stub attribute
item.attribute(parser.DEFAULT_ATTR_NAME, null);
if (attrList.length) {
// target for default value:
// 1. default attribute
// 2. implied attribute
// 3. first empty attribute
// find attribute marked as default
var defaultAttr = utils.find(attrList, findDefault)
|| utils.find(attrList, findImplied)
|| utils.find(attrList, findEmpty);
if (defaultAttr) {
var oldVal = item.attribute(defaultAttr.name);
var newVal = utils.replaceUnescapedSymbol(oldVal, '|', defaultAttrValue);
// no replacement, e.g. default value does not contains | symbol
if (oldVal == newVal) {
newVal = defaultAttrValue
}
item.attribute(defaultAttr.name, newVal);
}
}
} else {
// if no default attribute value, remove implied attributes
attrList.forEach(function(attr) {
if (attr.isImplied) {
item.attribute(attr.name, null);
}
});
}
resolveDefaultAttrs(item, parser);
});
}
return {
/**
* @param {AbbreviationNode} tree
* @param {Object} options
* @param {abbreviation} parser
*/
preprocessor: function(tree, options, parser) {
resolveDefaultAttrs(tree, parser);
}
};
});
},{"../../utils/common":"utils/common.js"}],"parser/processor/href.js":[function(require,module,exports){
/**
* A preptocessor for <a> tag: tests wrapped content
* for common URL patterns and, if matched, inserts it as
* `href` attribute
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var prefs = require('../../assets/preferences');
var utils = require('../../utils/common');
var pc = require('./pastedContent');
prefs.define('href.autodetect', true,
'Enables or disables automatic URL recognition when wrapping\
text with
<a>
tag. With this option enabled,\
if wrapped text matches URL or e-mail pattern it will be automatically\
inserted into
href
attribute.');
prefs.define('href.urlPattern', '^(?:(?:https?|ftp|file)://|www\\.|ftp\\.)(?:\\([-A-Z0-9+&@#/%=~_|$?!:,.]*\\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\\([-A-Z0-9+&@#/%=~_|$?!:,.]*\\)|[A-Z0-9+&@#/%=~_|$])',
'RegExp pattern to match wrapped URLs. Matched content will be inserts\
as-is into
href
attribute, only whitespace will be trimmed.');
prefs.define('href.emailPattern', '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,5}$',
'RegExp pattern to match wrapped e-mails. Unlike
href.urlPattern
,\
wrapped content will be prefixed with
mailto:
in
href
\
attribute');
return {
/**
* @param {AbbreviationNode} tree
* @param {Object} options
*/
postprocessor: function(tree, options) {
if (!prefs.get('href.autodetect')) {
return;
}
var reUrl = new RegExp(prefs.get('href.urlPattern'), 'i');
var reEmail = new RegExp(prefs.get('href.emailPattern'), 'i');
var reProto = /^([a-z]+:)?\/\//i;
tree.findAll(function(item) {
if (item.name().toLowerCase() != 'a' || item.attribute('href')) {
return;
}
var pastedContent = utils.trim(pc.pastedContent(item) || options.pastedContent);
if (pastedContent) {
if (reUrl.test(pastedContent)) {
// do we have protocol?
if (!reProto.test(pastedContent)) {
pastedContent = 'http://' + pastedContent;
}
item.attribute('href', pastedContent);
} else if (reEmail.test(pastedContent)) {
item.attribute('href', 'mailto:' + pastedContent);
}
}
});
}
};
});
},{"../../assets/preferences":"assets/preferences.js","../../utils/common":"utils/common.js","./pastedContent":"parser/processor/pastedContent.js"}],"parser/processor/pastedContent.js":[function(require,module,exports){
/**
* Pasted content abbreviation processor. A pasted content is a content that
* should be inserted into implicitly repeated abbreviation nodes.
* This processor powers “Wrap With Abbreviation” action
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../../utils/common');
var abbrUtils = require('../../utils/abbreviation');
var stringStream = require('../../assets/stringStream');
var range = require('../../assets/range');
var outputPlaceholder = '$#';
/**
* Locates output placeholders inside text
* @param {String} text
* @returns {Array} Array of ranges of output placeholder in text
*/
function locateOutputPlaceholder(text) {
var result = [];
var stream = stringStream.create(text);
while (!stream.eol()) {
if (stream.peek() == '\\') {
stream.next();
} else {
stream.start = stream.pos;
if (stream.match(outputPlaceholder, true)) {
result.push(range.create(stream.start, outputPlaceholder));
continue;
}
}
stream.next();
}
return result;
}
/**
* Replaces output placeholders inside
source
with
*
value
* @param {String} source
* @param {String} value
* @returns {String}
*/
function replaceOutputPlaceholders(source, value) {
var ranges = locateOutputPlaceholder(source);
ranges.reverse().forEach(function(r) {
source = utils.replaceSubstring(source, value, r);
});
return source;
}
/**
* Check if parsed node contains output placeholder – a target where
* pasted content should be inserted
* @param {AbbreviationNode} node
* @returns {Boolean}
*/
function hasOutputPlaceholder(node) {
if (locateOutputPlaceholder(node.content).length)
return true;
// check if attributes contains placeholder
return !!utils.find(node.attributeList(), function(attr) {
return !!locateOutputPlaceholder(attr.value).length;
});
}
/**
* Insert pasted content into correct positions of parsed node
* @param {AbbreviationNode} node
* @param {String} content
* @param {Boolean} overwrite Overwrite node content if no value placeholders
* found instead of appending to existing content
*/
function insertPastedContent(node, content, overwrite) {
var nodesWithPlaceholders = node.findAll(function(item) {
return hasOutputPlaceholder(item);
});
if (hasOutputPlaceholder(node))
nodesWithPlaceholders.unshift(node);
if (nodesWithPlaceholders.length) {
nodesWithPlaceholders.forEach(function(item) {
item.content = replaceOutputPlaceholders(item.content, content);
item._attributes.forEach(function(attr) {
attr.value = replaceOutputPlaceholders(attr.value, content);
});
});
} else {
// on output placeholders in subtree, insert content in the deepest
// child node
var deepest = node.deepestChild() || node;
if (overwrite) {
deepest.content = content;
} else {
deepest.content = abbrUtils.insertChildContent(deepest.content, content);
}
}
}
return {
pastedContent: function(item) {
var content = item.data('paste');
if (Array.isArray(content)) {
return content[item.counter - 1];
} else if (typeof content === 'function') {
return content(item.counter - 1, item.content);
} else if (content) {
return content;
}
},
/**
* @param {AbbreviationNode} tree
* @param {Object} options
*/
preprocessor: function(tree, options) {
if (options.pastedContent) {
var lines = utils.splitByLines(options.pastedContent, true).map(utils.trim);
// set repeat count for implicitly repeated elements before
// tree is unrolled
tree.findAll(function(item) {
if (item.hasImplicitRepeat) {
item.data('paste', lines);
return item.repeatCount = lines.length;
}
});
}
},
/**
* @param {AbbreviationNode} tree
* @param {Object} options
*/
postprocessor: function(tree, options) {
var that = this;
// for each node with pasted content, update text data
var targets = tree.findAll(function(item) {
var pastedContent = that.pastedContent(item);
if (pastedContent) {
insertPastedContent(item, pastedContent, !!item.data('pasteOverwrites'));
}
return !!pastedContent;
});
if (!targets.length && options.pastedContent) {
// no implicitly repeated elements, put pasted content in
// the deepest child
insertPastedContent(tree, options.pastedContent);
}
}
};
});
},{"../../assets/range":"assets/range.js","../../assets/stringStream":"assets/stringStream.js","../../utils/abbreviation":"utils/abbreviation.js","../../utils/common":"utils/common.js"}],"parser/processor/resourceMatcher.js":[function(require,module,exports){
/**
* Processor function that matches parsed
AbbreviationNode
* against resources defined in
resource
module
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var resources = require('../../assets/resources');
var elements = require('../../assets/elements');
var utils = require('../../utils/common');
var abbreviationUtils = require('../../utils/abbreviation');
/**
* Finds matched resources for child nodes of passed
node
* element. A matched resource is a reference to
snippets.json entry
* that describes output of parsed node
* @param {AbbreviationNode} node
* @param {String} syntax
*/
function matchResources(node, syntax, parser) {
// do a shallow copy because the children list can be modified during
// resource matching
node.children.slice(0).forEach(function(child) {
var r = resources.getMatchedResource(child, syntax);
if (typeof r === 'string') {
r = elements.create('snippet', r);
}
child.data('resource', r);
var elemType = elements.type(r);
if (elemType == 'snippet') {
var content = r.data;
var curContent = child._text || child.content;
if (curContent) {
content = abbreviationUtils.insertChildContent(content, curContent);
}
child.content = content;
} else if (elemType == 'element') {
child._name = r.name;
if (Array.isArray(r.attributes)) {
child._attributes = [].concat(r.attributes, child._attributes);
}
} else if (elemType == 'reference') {
// it’s a reference to another abbreviation:
// parse it and insert instead of current child
/** @type AbbreviationNode */
var subtree = parser.parse(r.data, {
syntax: syntax
});
// if context element should be repeated, check if we need to
// transfer repeated element to specific child node
if (child.repeatCount > 1) {
var repeatedChildren = subtree.findAll(function(node) {
return node.hasImplicitRepeat;
});
if (!repeatedChildren.length) {
repeatedChildren = subtree.children
}
repeatedChildren.forEach(function(node) {
node.repeatCount = child.repeatCount;
node.hasImplicitRepeat = false;
});
}
// move child‘s children into the deepest child of new subtree
var deepestChild = subtree.deepestChild();
if (deepestChild) {
child.children.forEach(function(c) {
deepestChild.addChild(c);
});
deepestChild.content = child.content;
}
// copy current attributes to children
subtree.children.forEach(function(node) {
child.attributeList().forEach(function(attr) {
node.attribute(attr.name, attr.value);
});
});
child.replace(subtree.children);
}
matchResources(child, syntax, parser);
});
}
return {
preprocessor: function(tree, options, parser) {
var syntax = options.syntax || utils.defaultSyntax();
matchResources(tree, syntax, parser);
}
};
});
},{"../../assets/elements":"assets/elements.js","../../assets/resources":"assets/resources.js","../../utils/abbreviation":"utils/abbreviation.js","../../utils/common":"utils/common.js"}],"parser/processor/tagName.js":[function(require,module,exports){
/**
* Resolves tag names in abbreviations with implied name
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var tagName = require('../../resolver/tagName');
/**
* Resolves implicit node names in parsed tree
* @param {AbbreviationNode} tree
*/
function resolveNodeNames(tree) {
tree.children.forEach(function(node) {
if (node.hasImplicitName() || node.data('forceNameResolving')) {
node._name = tagName.resolve(node.parent.name());
node.data('nameResolved', true);
}
resolveNodeNames(node);
});
return tree;
}
return {
postprocessor: resolveNodeNames
};
});
},{"../../resolver/tagName":"resolver/tagName.js"}],"parser/xml.js":[function(require,module,exports){
/**
* HTML tokenizer by Marijn Haverbeke
* http://codemirror.net/
* @constructor
* @memberOf __xmlParseDefine
* @param {Function} require
* @param {Underscore} _
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var stringStream = require('../assets/stringStream');
var Kludges = {
autoSelfClosers : {},
implicitlyClosed : {},
contextGrabbers : {},
doNotIndent : {},
allowUnquoted : true,
allowMissing : true
};
// Return variables for tokenizers
var tagName = null, type = null;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA["))
return chain(inBlock("atom", "]]>"));
else
return null;
} else if (stream.match("--"))
return chain(inBlock("comment", "-->"));
else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else
return null;
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
} else {
type = stream.eat("/") ? "closeTag" : "openTag";
stream.eatSpace();
tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/)))
tagName += c;
state.tokenize = inTag;
return "tag";
}
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
} else {
stream.eatWhile(/[^&<]/);
return "text";
}
}
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag";
} else if (ch == "=") {
type = "equals";
return null;
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
return state.tokenize(stream, state);
} else {
stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
return "word";
}
}
function inAttribute(quote) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
};
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) !== null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
var curState = null, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--)
curState.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName)
|| (curState.context && curState.context.noIndent);
curState.context = {
prev : curState.context,
tagName : tagName,
indent : curState.indented,
startOfLine : startOfLine,
noIndent : noIndent
};
}
function popContext() {
if (curState.context)
curState.context = curState.context.prev;
}
function element(type) {
if (type == "openTag") {
curState.tagName = tagName;
return cont(attributes, endtag(curState.startOfLine));
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
if (curState.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
popContext();
}
err = !curState.context || curState.context.tagName != tagName;
}
} else {
err = true;
}
if (err)
setStyle = "error";
return cont(endclosetag(err));
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
if (type == "selfcloseTag"
|| (type == "endTag" && Kludges.autoSelfClosers
.hasOwnProperty(curState.tagName
.toLowerCase()))) {
maybePopContext(curState.tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
maybePopContext(curState.tagName.toLowerCase());
pushContext(curState.tagName, startOfLine);
return cont();
}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err)
setStyle = "error";
if (type == "endTag") {
popContext();
return cont();
}
setStyle = "error";
return cont(arguments.callee);
};
}
function maybePopContext(nextTagName) {
var parentTagName;
while (true) {
if (!curState.context) {
return;
}
parentTagName = curState.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName)
|| !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext();
}
}
function attributes(type) {
if (type == "word") {
setStyle = "attribute";
return cont(attribute, attributes);
}
if (type == "endTag" || type == "selfcloseTag")
return pass();
setStyle = "error";
return cont(attributes);
}
function attribute(type) {
if (type == "equals")
return cont(attvalue, attributes);
if (!Kludges.allowMissing)
setStyle = "error";
return (type == "endTag" || type == "selfcloseTag") ? pass()
: cont();
}
function attvalue(type) {
if (type == "string")
return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {
setStyle = "string";
return cont();
}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass()
: cont();
}
function attvaluemaybe(type) {
if (type == "string")
return cont(attvaluemaybe);
else
return pass();
}
function startState() {
return {
tokenize : inText,
cc : [],
indented : 0,
startOfLine : true,
tagName : null,
context : null
};
}
function token(stream, state) {
if (stream.sol()) {
state.startOfLine = true;
state.indented = 0;
}
if (stream.eatSpace())
return null;
setStyle = type = tagName = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style))
break;
}
}
state.startOfLine = false;
return setStyle || style;
}
return {
/**
* @memberOf emmet.xmlParser
* @returns
*/
parse: function(data, offset) {
offset = offset || 0;
var state = startState();
var stream = stringStream.create(data);
var tokens = [];
while (!stream.eol()) {
tokens.push({
type: token(stream, state),
start: stream.start + offset,
end: stream.pos + offset
});
stream.start = stream.pos;
}
return tokens;
}
};
});
},{"../assets/stringStream":"assets/stringStream.js"}],"plugin/file.js":[function(require,module,exports){
/**
* Module for working with file. Shall implement
* IEmmetFile interface.
*
* Since implementation of this module depends
* greatly on current runtime, this module must be
* initialized with actual implementation first
* before use. E.g.
* require('./plugin/file')({
* read: function() {...}
* })
*
* By default, this module provides Node.JS implementation
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
// hide it from Require.JS parser
(function(r) {
if (typeof define === 'undefined' || !define.amd) {
try {
fs = r('fs');
path = r('path');
} catch(e) {}
}
})(require);
// module is a function that can extend itself
module.exports = function(obj) {
if (obj) {
utils.extend(module.exports, obj);
}
};
function bts(bytes) {
var out = [];
for (var i = 0, il = bytes.length; i < il; i++) {
out.push(String.fromCharCode(bytes[i]));
}
return out.join('');
}
function isURL(path) {
var re = /^https?:\/\//;
return re.test(path);
}
return utils.extend(module.exports, {
_parseParams: function(args) {
var params = {
path: args[0],
size: 0
};
args = utils.toArray(args, 1);
params.callback = args[args.length - 1];
args = args.slice(0, args.length - 1);
if (args.length) {
params.size = args[0];
}
return params;
},
_read: function(params, callback) {
if (isURL(params.path)) {
var req = require(/^https:/.test(params.path) ? 'https' : 'http').get(params.path, function(res) {
var bufs = [];
var totalLength = 0;
var finished = false;
res
.on('data', function(chunk) {
totalLength += chunk.length;
bufs.push(chunk);
if (params.size && totalLength >= params.size) {
finished = true;
callback(null, Buffer.concat(bufs));
req.abort();
}
})
.on('end', function() {
if (!finished) {
finished = true;
callback(null, Buffer.concat(bufs));
}
});
}).on('error', callback);
} else {
if (params.size) {
var fd = fs.openSync(params.path, 'r');
var buf = new Buffer(params.size);
fs.read(fd, buf, 0, params.size, null, function(err, bytesRead) {
callback(err, buf)
});
} else {
callback(null, fs.readFileSync(params.path));
}
}
},
/**
* Reads binary file content and return it
* @param {String} path File's relative or absolute path
* @return {String}
*/
read: function(path, size, callback) {
var params = this._parseParams(arguments);
this._read(params, function(err, buf) {
params.callback(err, err ? '' : bts(buf));
});
},
/**
* Read file content and return it
* @param {String} path File's relative or absolute path
* @return {String}
*/
readText: function(path, size, callback) {
var params = this._parseParams(arguments);
this._read(params, function(err, buf) {
params.callback(err, err ? '' : buf.toString());
});
},
/**
* Locate
file_name
file that relates to
editor_file
.
* File name may be absolute or relative path
*
*
Dealing with absolute path.
* Many modern editors have a "project" support as information unit, but you
* should not rely on project path to find file with absolute path. First,
* it requires user to create a project before using this method (and this
* is not very convenient). Second, project path doesn't always points to
* to website's document root folder: it may point, for example, to an
* upper folder which contains server-side scripts.
*
* For better result, you should use the following algorithm in locating
* absolute resources:
* 1) Get parent folder for
editorFile
as a start point
* 2) Append required
fileName
to start point and test if
* file exists
* 3) If it doesn't exists, move start point one level up (to parent folder)
* and repeat step 2.
*
* @param {String} editorFile
* @param {String} fileName
* @return {String} Returns null if
fileName
cannot be located
*/
locateFile: function(editorFile, fileName) {
if (isURL(fileName)) {
return fileName;
}
var dirname = editorFile, f;
fileName = fileName.replace(/^\/+/, '');
while (dirname && dirname !== path.dirname(dirname)) {
dirname = path.dirname(dirname);
f = path.join(dirname, fileName);
if (fs.existsSync(f))
return f;
}
return '';
},
/**
* Creates absolute path by concatenating
parent
and
fileName
.
* If
parent
points to file, its parent directory is used
* @param {String} parent
* @param {String} fileName
* @return {String}
*/
createPath: function(parent, fileName, callback) {
var stat = fs.statSync(parent);
if (stat && !stat.isDirectory()) {
parent = path.dirname(parent);
}
return callback(path.resolve(parent, fileName));
},
/**
* Saves
content
as
file
* @param {String} file File's absolute path
* @param {String} content File content
*/
save: function(file, content) {
fs.writeFileSync(file, content, 'ascii');
},
/**
* Returns file extension in lower case
* @param {String} file
* @return {String}
*/
getExt: function(file) {
var m = (file || '').match(/\.([\w\-]+)$/);
return m ? m[1].toLowerCase() : '';
}
});
});
},{"../utils/common":"utils/common.js"}],"resolver/css.js":[function(require,module,exports){
/**
* Resolver for fast CSS typing. Handles abbreviations with the following
* notation:
*
*
(-vendor prefix)?property(value)*(!)?
*
*
*
Abbreviation handling
*
* By default, Emmet searches for matching snippet definition for provided abbreviation.
* If snippet wasn't found, Emmet automatically generates element with
* abbreviation's name. For example,
foo
abbreviation will generate
*
<foo></foo>
output.
*
* This module will capture all expanded properties and upgrade them with values,
* vendor prefixes and !important declarations. All unmatched abbreviations will
* be automatically transformed into
property-name: ${1}
snippets.
*
*
Vendor prefixes
*
* If CSS-property is preceded with dash, resolver should output property with
* all known vendor prefixes. For example, if brad
* abbreviation generates border-radius: ${value};
snippet,
* the -brad
abbreviation should generate:
*
* -webkit-border-radius: ${value};
* -moz-border-radius: ${value};
* border-radius: ${value};
*
* Note that o and ms prefixes are omitted since Opera and IE
* supports unprefixed property.
*
* Users can also provide an explicit list of one-character prefixes for any
* CSS property. For example, -wm-float
will produce
*
*
* -webkit-float: ${1};
* -moz-float: ${1};
* float: ${1};
*
*
* Although this example looks pointless, users can use this feature to write
* cutting-edge properties implemented by browser vendors recently.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var prefs = require('../assets/preferences');
var resources = require('../assets/resources');
var stringStream = require('../assets/stringStream');
var ciu = require('../assets/caniuse');
var utils = require('../utils/common');
var template = require('../utils/template');
var cssEditTree = require('../editTree/css');
var prefixObj = {
/** Real vendor prefix name */
prefix: 'emmet',
/**
* Indicates this prefix is obsolete and should't be used when user
* wants to generate all-prefixed properties
*/
obsolete: false,
/**
* Returns prefixed CSS property name
* @param {String} name Unprefixed CSS property
*/
transformName: function(name) {
return '-' + this.prefix + '-' + name;
},
/**
* List of unprefixed CSS properties that supported by
* current prefix. This list is used to generate all-prefixed property
* @returns {Array}
*/
properties: function() {
return getProperties('css.' + this.prefix + 'Properties') || [];
},
/**
* Check if given property is supported by current prefix
* @param name
*/
supports: function(name) {
return ~this.properties().indexOf(name);
}
};
/**
* List of registered one-character prefixes. Key is a one-character prefix,
* value is an prefixObj
object
*/
var vendorPrefixes = {};
var defaultValue = '${1};';
// XXX module preferences
prefs.define('css.valueSeparator', ': ',
'Defines a symbol that should be placed between CSS property and '
+ 'value when expanding CSS abbreviations.');
prefs.define('css.propertyEnd', ';',
'Defines a symbol that should be placed at the end of CSS property '
+ 'when expanding CSS abbreviations.');
prefs.define('stylus.valueSeparator', ' ',
'Defines a symbol that should be placed between CSS property and '
+ 'value when expanding CSS abbreviations in Stylus dialect.');
prefs.define('stylus.propertyEnd', '',
'Defines a symbol that should be placed at the end of CSS property '
+ 'when expanding CSS abbreviations in Stylus dialect.');
prefs.define('sass.propertyEnd', '',
'Defines a symbol that should be placed at the end of CSS property '
+ 'when expanding CSS abbreviations in SASS dialect.');
prefs.define('css.syntaxes', 'css, less, sass, scss, stylus, styl',
'List of syntaxes that should be treated as CSS dialects.');
prefs.define('css.autoInsertVendorPrefixes', true,
'Automatically generate vendor-prefixed copies of expanded CSS '
+ 'property. By default, Emmet will generate vendor-prefixed '
+ 'properties only when you put dash before abbreviation '
+ '(e.g. -bxsh
). With this option enabled, you don’t '
+ 'need dashes before abbreviations: Emmet will produce '
+ 'vendor-prefixed properties for you.');
prefs.define('less.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes
but for LESS syntax');
prefs.define('scss.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes
but for SCSS syntax');
prefs.define('sass.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes
but for SASS syntax');
prefs.define('stylus.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes
but for Stylus syntax');
var descTemplate = template('A comma-separated list of CSS properties that may have '
+ '<%= vendor %>
vendor prefix. This list is used to generate '
+ 'a list of prefixed properties when expanding -property
'
+ 'abbreviations. Empty list means that all possible CSS values may '
+ 'have <%= vendor %>
prefix.');
var descAddonTemplate = template('A comma-separated list of additional CSS properties '
+ 'for css.<%= vendor %>Preperties
preference. '
+ 'You should use this list if you want to add or remove a few CSS '
+ 'properties to original set. To add a new property, simply write its name, '
+ 'to remove it, precede property with hyphen.
'
+ 'For example, to add foo property and remove border-radius one, '
+ 'the preference value will look like this: foo, -border-radius
.');
// properties list is created from cssFeatures.html file
var props = {
'webkit': 'animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, appearance, backface-visibility, background-clip, background-composite, background-origin, background-size, border-fit, border-horizontal-spacing, border-image, border-vertical-spacing, box-align, box-direction, box-flex, box-flex-group, box-lines, box-ordinal-group, box-orient, box-pack, box-reflect, box-shadow, color-correction, column-break-after, column-break-before, column-break-inside, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-span, column-width, dashboard-region, font-smoothing, highlight, hyphenate-character, hyphenate-limit-after, hyphenate-limit-before, hyphens, line-box-contain, line-break, line-clamp, locale, margin-before-collapse, margin-after-collapse, marquee-direction, marquee-increment, marquee-repetition, marquee-style, mask-attachment, mask-box-image, mask-box-image-outset, mask-box-image-repeat, mask-box-image-slice, mask-box-image-source, mask-box-image-width, mask-clip, mask-composite, mask-image, mask-origin, mask-position, mask-repeat, mask-size, nbsp-mode, perspective, perspective-origin, rtl-ordering, text-combine, text-decorations-in-effect, text-emphasis-color, text-emphasis-position, text-emphasis-style, text-fill-color, text-orientation, text-security, text-stroke-color, text-stroke-width, transform, transition, transform-origin, transform-style, transition-delay, transition-duration, transition-property, transition-timing-function, user-drag, user-modify, user-select, writing-mode, svg-shadow, box-sizing, border-radius',
'moz': 'animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, appearance, backface-visibility, background-inline-policy, binding, border-bottom-colors, border-image, border-left-colors, border-right-colors, border-top-colors, box-align, box-direction, box-flex, box-ordinal-group, box-orient, box-pack, box-shadow, box-sizing, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-width, float-edge, font-feature-settings, font-language-override, force-broken-image-icon, hyphens, image-region, orient, outline-radius-bottomleft, outline-radius-bottomright, outline-radius-topleft, outline-radius-topright, perspective, perspective-origin, stack-sizing, tab-size, text-blink, text-decoration-color, text-decoration-line, text-decoration-style, text-size-adjust, transform, transform-origin, transform-style, transition, transition-delay, transition-duration, transition-property, transition-timing-function, user-focus, user-input, user-modify, user-select, window-shadow, background-clip, border-radius',
'ms': 'accelerator, backface-visibility, background-position-x, background-position-y, behavior, block-progression, box-align, box-direction, box-flex, box-line-progression, box-lines, box-ordinal-group, box-orient, box-pack, content-zoom-boundary, content-zoom-boundary-max, content-zoom-boundary-min, content-zoom-chaining, content-zoom-snap, content-zoom-snap-points, content-zoom-snap-type, content-zooming, filter, flow-from, flow-into, font-feature-settings, grid-column, grid-column-align, grid-column-span, grid-columns, grid-layer, grid-row, grid-row-align, grid-row-span, grid-rows, high-contrast-adjust, hyphenate-limit-chars, hyphenate-limit-lines, hyphenate-limit-zone, hyphens, ime-mode, interpolation-mode, layout-flow, layout-grid, layout-grid-char, layout-grid-line, layout-grid-mode, layout-grid-type, line-break, overflow-style, perspective, perspective-origin, perspective-origin-x, perspective-origin-y, scroll-boundary, scroll-boundary-bottom, scroll-boundary-left, scroll-boundary-right, scroll-boundary-top, scroll-chaining, scroll-rails, scroll-snap-points-x, scroll-snap-points-y, scroll-snap-type, scroll-snap-x, scroll-snap-y, scrollbar-arrow-color, scrollbar-base-color, scrollbar-darkshadow-color, scrollbar-face-color, scrollbar-highlight-color, scrollbar-shadow-color, scrollbar-track-color, text-align-last, text-autospace, text-justify, text-kashida-space, text-overflow, text-size-adjust, text-underline-position, touch-action, transform, transform-origin, transform-origin-x, transform-origin-y, transform-origin-z, transform-style, transition, transition-delay, transition-duration, transition-property, transition-timing-function, user-select, word-break, wrap-flow, wrap-margin, wrap-through, writing-mode',
'o': 'dashboard-region, animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, border-image, link, link-source, object-fit, object-position, tab-size, table-baseline, transform, transform-origin, transition, transition-delay, transition-duration, transition-property, transition-timing-function, accesskey, input-format, input-required, marquee-dir, marquee-loop, marquee-speed, marquee-style'
};
Object.keys(props).forEach(function(k) {
prefs.define('css.' + k + 'Properties', props[k], descTemplate({vendor: k}));
prefs.define('css.' + k + 'PropertiesAddon', '', descAddonTemplate({vendor: k}));
});
prefs.define('css.unitlessProperties', 'z-index, line-height, opacity, font-weight, zoom',
'The list of properties whose values must not contain units.');
prefs.define('css.intUnit', 'px', 'Default unit for integer values');
prefs.define('css.floatUnit', 'em', 'Default unit for float values');
prefs.define('css.keywords', 'auto, inherit, all',
'A comma-separated list of valid keywords that can be used in CSS abbreviations.');
prefs.define('css.keywordAliases', 'a:auto, i:inherit, s:solid, da:dashed, do:dotted, t:transparent',
'A comma-separated list of keyword aliases, used in CSS abbreviation. '
+ 'Each alias should be defined as alias:keyword_name
.');
prefs.define('css.unitAliases', 'e:em, p:%, x:ex, r:rem',
'A comma-separated list of unit aliases, used in CSS abbreviation. '
+ 'Each alias should be defined as alias:unit_value
.');
prefs.define('css.color.short', true,
'Should color values like #ffffff
be shortened to '
+ '#fff
after abbreviation with color was expanded.');
prefs.define('css.color.case', 'keep',
'Letter case of color values generated by abbreviations with color '
+ '(like c#0
). Possible values are upper
, '
+ 'lower
and keep
.');
prefs.define('css.fuzzySearch', true,
'Enable fuzzy search among CSS snippet names. When enabled, every '
+ 'unknown snippet will be scored against available snippet '
+ 'names (not values or CSS properties!). The match with best score '
+ 'will be used to resolve snippet value. For example, with this '
+ 'preference enabled, the following abbreviations are equal: '
+ 'ov:h
== ov-h
== o-h
== '
+ 'oh
');
prefs.define('css.fuzzySearchMinScore', 0.3,
'The minium score (from 0 to 1) that fuzzy-matched abbreviation should '
+ 'achive. Lower values may produce many false-positive matches, '
+ 'higher values may reduce possible matches.');
prefs.define('css.alignVendor', false,
'If set to true
, all generated vendor-prefixed properties '
+ 'will be aligned by real property name.');
function isNumeric(ch) {
var code = ch && ch.charCodeAt(0);
return (ch && ch == '.' || (code > 47 && code < 58));
}
/**
* Check if provided snippet contains only one CSS property and value.
* @param {String} snippet
* @returns {Boolean}
*/
function isSingleProperty(snippet) {
snippet = utils.trim(snippet);
// check if it doesn't contain a comment and a newline
if (/\/\*|\n|\r/.test(snippet)) {
return false;
}
// check if it's a valid snippet definition
if (!/^[a-z0-9\-]+\s*\:/i.test(snippet)) {
return false;
}
return snippet.replace(/\$\{.+?\}/g, '').split(':').length == 2;
}
/**
* Normalizes abbreviated value to final CSS one
* @param {String} value
* @returns {String}
*/
function normalizeValue(value) {
if (value.charAt(0) == '-' && !/^\-[\.\d]/.test(value)) {
value = value.replace(/^\-+/, '');
}
var ch = value.charAt(0);
if (ch == '#') {
return normalizeHexColor(value);
}
if (ch == '$') {
return utils.escapeText(value);
}
return getKeyword(value);
}
function normalizeHexColor(value) {
var hex = value.replace(/^#+/, '') || '0';
if (hex.toLowerCase() == 't') {
return 'transparent';
}
var opacity = '';
hex = hex.replace(/\.(\d+)$/, function(str) {
opacity = '0' + str;
return '';
});
var repeat = utils.repeatString;
var color = null;
switch (hex.length) {
case 1:
color = repeat(hex, 6);
break;
case 2:
color = repeat(hex, 3);
break;
case 3:
color = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
break;
case 4:
color = hex + hex.substr(0, 2);
break;
case 5:
color = hex + hex.charAt(0);
break;
default:
color = hex.substr(0, 6);
}
if (opacity) {
return toRgba(color, opacity);
}
// color must be shortened?
if (prefs.get('css.color.short')) {
var p = color.split('');
if (p[0] == p[1] && p[2] == p[3] && p[4] == p[5]) {
color = p[0] + p[2] + p[4];
}
}
// should transform case?
switch (prefs.get('css.color.case')) {
case 'upper':
color = color.toUpperCase();
break;
case 'lower':
color = color.toLowerCase();
break;
}
return '#' + color;
}
/**
* Transforms HEX color definition into RGBA one
* @param {String} color HEX color, 6 characters
* @param {String} opacity Opacity value
* @return {String}
*/
function toRgba(color, opacity) {
var r = parseInt(color.substr(0, 2), 16);
var g = parseInt(color.substr(2, 2), 16);
var b = parseInt(color.substr(4, 2), 16);
return 'rgba(' + [r, g, b, opacity].join(', ') + ')';
}
function getKeyword(name) {
var aliases = prefs.getDict('css.keywordAliases');
return name in aliases ? aliases[name] : name;
}
function getUnit(name) {
var aliases = prefs.getDict('css.unitAliases');
return name in aliases ? aliases[name] : name;
}
function isValidKeyword(keyword) {
return ~prefs.getArray('css.keywords').indexOf(getKeyword(keyword));
}
/**
* Check if passed CSS property support specified vendor prefix
* @param {String} property
* @param {String} prefix
*/
function hasPrefix(property, prefix) {
var info = vendorPrefixes[prefix];
if (!info)
info = utils.find(vendorPrefixes, function(data) {
return data.prefix == prefix;
});
return info && info.supports(property);
}
/**
* Finds available vendor prefixes for given CSS property.
* Search is performed within Can I Use database and internal
* property list
* @param {String} property CSS property name
* @return {Array} Array of resolved prefixes or null if
* prefixes are not available for this property at all.
* Empty array means prefixes are not available for current
* user-define era
*/
function findVendorPrefixes(property) {
var prefixes = ciu.resolvePrefixes(property);
if (!prefixes) {
// Can I Use database is disabled or prefixes are not
// available for this property
prefixes = [];
Object.keys(vendorPrefixes).forEach(function(key) {
if (hasPrefix(property, key)) {
prefixes.push(vendorPrefixes[key].prefix);
}
});
if (!prefixes.length) {
prefixes = null;
}
}
return prefixes;
}
/**
* Search for a list of supported prefixes for CSS property. This list
* is used to generate all-prefixed snippet
* @param {String} property CSS property name
* @returns {Array}
*/
function findInternalPrefixes(property, noAutofill) {
var result = [];
var prefixes = findVendorPrefixes(property);
if (prefixes) {
var prefixMap = {};
Object.keys(vendorPrefixes).forEach(function(key) {
prefixMap[vendorPrefixes[key].prefix] = key;
});
result = prefixes.map(function(prefix) {
return prefixMap[prefix];
});
}
if (!result.length && !noAutofill) {
// add all non-obsolete prefixes
Object.keys(vendorPrefixes).forEach(function(prefix) {
if (!vendorPrefixes[prefix].obsolete) {
result.push(prefix);
}
});
}
return result;
}
function addPrefix(name, obj) {
if (typeof obj === 'string') {
obj = {prefix: obj};
}
vendorPrefixes[name] = utils.extend({}, prefixObj, obj);
}
function getSyntaxPreference(name, syntax) {
if (syntax) {
// hacky alias for Stylus dialect
if (syntax == 'styl') {
syntax = 'stylus';
}
var val = prefs.get(syntax + '.' + name);
if (typeof val !== 'undefined') {
return val;
}
}
return prefs.get('css.' + name);
}
/**
* Format CSS property according to current syntax dialect
* @param {String} property
* @param {String} syntax
* @returns {String}
*/
function formatProperty(property, syntax) {
var ix = property.indexOf(':');
property = property.substring(0, ix).replace(/\s+$/, '')
+ getSyntaxPreference('valueSeparator', syntax)
+ utils.trim(property.substring(ix + 1));
return property.replace(/\s*;\s*$/, getSyntaxPreference('propertyEnd', syntax));
}
/**
* Transforms snippet value if required. For example, this transformation
* may add !important declaration to CSS property
* @param {String} snippet
* @param {Boolean} isImportant
* @returns {String}
*/
function transformSnippet(snippet, isImportant, syntax) {
if (typeof snippet !== 'string') {
snippet = snippet.data;
}
if (!isSingleProperty(snippet)) {
return snippet;
}
if (isImportant) {
if (~snippet.indexOf(';')) {
snippet = snippet.split(';').join(' !important;');
} else {
snippet += ' !important';
}
}
return formatProperty(snippet, syntax);
}
function getProperties(key) {
var list = prefs.getArray(key);
var addon = prefs.getArray(key + 'Addon');
if (addon) {
addon.forEach(function(prop) {
if (prop.charAt(0) == '-') {
list = utils.without(list, prop.substr(1));
} else {
if (prop.charAt(0) == '+')
prop = prop.substr(1);
list.push(prop);
}
});
}
return list;
}
/**
* Tries to produce properties with vendor-prefixed value
* @param {Object} snippetObj Parsed snippet object
* @return {Array} Array of properties with prefixed values
*/
function resolvePrefixedValues(snippetObj, isImportant, syntax) {
var prefixes = [];
var lookup = {};
var parts = cssEditTree.findParts(snippetObj.value);
parts.reverse();
parts.forEach(function(p) {
var partValue = p.substring(snippetObj.value);
(findVendorPrefixes(partValue) || []).forEach(function(prefix) {
if (!lookup[prefix]) {
lookup[prefix] = snippetObj.value;
prefixes.push(prefix);
}
lookup[prefix] = utils.replaceSubstring(lookup[prefix], '-' + prefix + '-' + partValue, p);
});
});
return prefixes.map(function(prefix) {
return transformSnippet(snippetObj.name + ':' + lookup[prefix], isImportant, syntax);
});
}
// TODO refactor, this looks awkward now
addPrefix('w', {
prefix: 'webkit'
});
addPrefix('m', {
prefix: 'moz'
});
addPrefix('s', {
prefix: 'ms'
});
addPrefix('o', {
prefix: 'o'
});
module = module || {};
module.exports = {
/**
* Adds vendor prefix
* @param {String} name One-character prefix name
* @param {Object} obj Object describing vendor prefix
* @memberOf cssResolver
*/
addPrefix: addPrefix,
/**
* Check if passed CSS property supports specified vendor prefix
* @param {String} property
* @param {String} prefix
*/
supportsPrefix: hasPrefix,
resolve: function(node, syntax) {
var cssSyntaxes = prefs.getArray('css.syntaxes');
if (cssSyntaxes && ~cssSyntaxes.indexOf(syntax) && node.isElement()) {
return this.expandToSnippet(node.abbreviation, syntax);
}
return null;
},
/**
* Returns prefixed version of passed CSS property, only if this
* property supports such prefix
* @param {String} property
* @param {String} prefix
* @returns
*/
prefixed: function(property, prefix) {
return hasPrefix(property, prefix)
? '-' + prefix + '-' + property
: property;
},
/**
* Returns list of all registered vendor prefixes
* @returns {Array}
*/
listPrefixes: function() {
return vendorPrefixes.map(function(obj) {
return obj.prefix;
});
},
/**
* Returns object describing vendor prefix
* @param {String} name
* @returns {Object}
*/
getPrefix: function(name) {
return vendorPrefixes[name];
},
/**
* Removes prefix object
* @param {String} name
*/
removePrefix: function(name) {
if (name in vendorPrefixes)
delete vendorPrefixes[name];
},
/**
* Extract vendor prefixes from abbreviation
* @param {String} abbr
* @returns {Object} Object containing array of prefixes and clean
* abbreviation name
*/
extractPrefixes: function(abbr) {
if (abbr.charAt(0) != '-') {
return {
property: abbr,
prefixes: null
};
}
// abbreviation may either contain sequence of one-character prefixes
// or just dash, meaning that user wants to produce all possible
// prefixed properties
var i = 1, il = abbr.length, ch;
var prefixes = [];
while (i < il) {
ch = abbr.charAt(i);
if (ch == '-') {
// end-sequence character found, stop searching
i++;
break;
}
if (ch in vendorPrefixes) {
prefixes.push(ch);
} else {
// no prefix found, meaning user want to produce all
// vendor-prefixed properties
prefixes.length = 0;
i = 1;
break;
}
i++;
}
// reached end of abbreviation and no property name left
if (i == il -1) {
i = 1;
prefixes.length = 1;
}
return {
property: abbr.substring(i),
prefixes: prefixes.length ? prefixes : 'all'
};
},
/**
* Search for value substring in abbreviation
* @param {String} abbr
* @returns {String} Value substring
*/
findValuesInAbbreviation: function(abbr, syntax) {
syntax = syntax || 'css';
var i = 0, il = abbr.length, value = '', ch;
while (i < il) {
ch = abbr.charAt(i);
if (isNumeric(ch) || ch == '#' || ch == '$' || (ch == '-' && isNumeric(abbr.charAt(i + 1)))) {
value = abbr.substring(i);
break;
}
i++;
}
// try to find keywords in abbreviation
var property = abbr.substring(0, abbr.length - value.length);
var keywords = [];
// try to extract some commonly-used properties
while (~property.indexOf('-') && !resources.findSnippet(syntax, property)) {
var parts = property.split('-');
var lastPart = parts.pop();
if (!isValidKeyword(lastPart)) {
break;
}
keywords.unshift(lastPart);
property = parts.join('-');
}
return keywords.join('-') + value;
},
parseValues: function(str) {
/** @type StringStream */
var stream = stringStream.create(str);
var values = [];
var ch = null;
while ((ch = stream.next())) {
if (ch == '$') {
stream.match(/^[^\$]+/, true);
values.push(stream.current());
} else if (ch == '#') {
stream.match(/^t|[0-9a-f]+(\.\d+)?/i, true);
values.push(stream.current());
} else if (ch == '-') {
if (isValidKeyword(utils.last(values)) ||
( stream.start && isNumeric(str.charAt(stream.start - 1)) )
) {
stream.start = stream.pos;
}
stream.match(/^\-?[0-9]*(\.[0-9]+)?[a-z%\.]*/, true);
values.push(stream.current());
} else {
stream.match(/^[0-9]*(\.[0-9]*)?[a-z%]*/, true);
values.push(stream.current());
}
stream.start = stream.pos;
}
return values
.filter(function(item) {
return !!item;
})
.map(normalizeValue);
},
/**
* Extracts values from abbreviation
* @param {String} abbr
* @returns {Object} Object containing array of values and clean
* abbreviation name
*/
extractValues: function(abbr) {
// search for value start
var abbrValues = this.findValuesInAbbreviation(abbr);
if (!abbrValues) {
return {
property: abbr,
values: null
};
}
return {
property: abbr.substring(0, abbr.length - abbrValues.length).replace(/-$/, ''),
values: this.parseValues(abbrValues)
};
},
/**
* Normalizes value, defined in abbreviation.
* @param {String} value
* @param {String} property
* @returns {String}
*/
normalizeValue: function(value, property) {
property = (property || '').toLowerCase();
var unitlessProps = prefs.getArray('css.unitlessProperties');
return value.replace(/^(\-?[0-9\.]+)([a-z]*)$/, function(str, val, unit) {
if (!unit && (val == '0' || ~unitlessProps.indexOf(property)))
return val;
if (!unit)
return val.replace(/\.$/, '') + prefs.get(~val.indexOf('.') ? 'css.floatUnit' : 'css.intUnit');
return val + getUnit(unit);
});
},
/**
* Expands abbreviation into a snippet
* @param {String} abbr Abbreviation name to expand
* @param {String} value Abbreviation value
* @param {String} syntax Currect syntax or dialect. Default is 'css'
* @returns {Object} Array of CSS properties and values or predefined
* snippet (string or element)
*/
expand: function(abbr, value, syntax) {
syntax = syntax || 'css';
var autoInsertPrefixes = prefs.get(syntax + '.autoInsertVendorPrefixes');
// check if snippet should be transformed to !important
var isImportant = /^(.+)\!$/.test(abbr);
if (isImportant) {
abbr = RegExp.$1;
}
// check if we have abbreviated resource
var snippet = resources.findSnippet(syntax, abbr);
if (snippet && !autoInsertPrefixes) {
return transformSnippet(snippet, isImportant, syntax);
}
// no abbreviated resource, parse abbreviation
var prefixData = this.extractPrefixes(abbr);
var valuesData = this.extractValues(prefixData.property);
var abbrData = utils.extend(prefixData, valuesData);
if (!snippet) {
snippet = resources.findSnippet(syntax, abbrData.property);
} else {
abbrData.values = null;
}
if (!snippet && prefs.get('css.fuzzySearch')) {
// let’s try fuzzy search
snippet = resources.fuzzyFindSnippet(syntax, abbrData.property, parseFloat(prefs.get('css.fuzzySearchMinScore')));
}
if (!snippet) {
if (!abbrData.property) {
return null;
}
snippet = abbrData.property + ':' + defaultValue;
} else if (typeof snippet !== 'string') {
snippet = snippet.data;
}
if (!isSingleProperty(snippet)) {
return snippet;
}
var snippetObj = this.splitSnippet(snippet);
var result = [];
if (!value && abbrData.values) {
value = abbrData.values.map(function(val) {
return this.normalizeValue(val, snippetObj.name);
}, this).join(' ') + ';';
}
snippetObj.value = value || snippetObj.value;
var prefixes = abbrData.prefixes == 'all' || (!abbrData.prefixes && autoInsertPrefixes)
? findInternalPrefixes(snippetObj.name, autoInsertPrefixes && abbrData.prefixes != 'all')
: abbrData.prefixes;
var names = [], propName;
(prefixes || []).forEach(function(p) {
if (p in vendorPrefixes) {
propName = vendorPrefixes[p].transformName(snippetObj.name);
names.push(propName);
result.push(transformSnippet(propName + ':' + snippetObj.value,
isImportant, syntax));
}
});
// put the original property
result.push(transformSnippet(snippetObj.name + ':' + snippetObj.value, isImportant, syntax));
names.push(snippetObj.name);
result = resolvePrefixedValues(snippetObj, isImportant, syntax).concat(result);
if (prefs.get('css.alignVendor')) {
var pads = utils.getStringsPads(names);
result = result.map(function(prop, i) {
return pads[i] + prop;
});
}
return result;
},
/**
* Same as expand
method but transforms output into
* Emmet snippet
* @param {String} abbr
* @param {String} syntax
* @returns {String}
*/
expandToSnippet: function(abbr, syntax) {
var snippet = this.expand(abbr, null, syntax);
if (snippet === null) {
return null;
}
if (Array.isArray(snippet)) {
return snippet.join('\n');
}
if (typeof snippet !== 'string') {
return snippet.data;
}
return snippet + '';
},
/**
* Split snippet into a CSS property-value pair
* @param {String} snippet
*/
splitSnippet: function(snippet) {
snippet = utils.trim(snippet);
if (snippet.indexOf(':') == -1) {
return {
name: snippet,
value: defaultValue
};
}
var pair = snippet.split(':');
return {
name: utils.trim(pair.shift()),
// replace ${0} tabstop to produce valid vendor-prefixed values
// where possible
value: utils.trim(pair.join(':')).replace(/^(\$\{0\}|\$0)(\s*;?)$/, '${1}$2')
};
},
getSyntaxPreference: getSyntaxPreference,
transformSnippet: transformSnippet,
vendorPrefixes: findVendorPrefixes
};
return module.exports;
});
},{"../assets/caniuse":"assets/caniuse.js","../assets/preferences":"assets/preferences.js","../assets/resources":"assets/resources.js","../assets/stringStream":"assets/stringStream.js","../editTree/css":"editTree/css.js","../utils/common":"utils/common.js","../utils/template":"utils/template.js"}],"resolver/cssGradient.js":[function(require,module,exports){
/**
* 'Expand Abbreviation' handler that parses gradient definition from under
* cursor and updates CSS rule with vendor-prefixed values.
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var prefs = require('../assets/preferences');
var resources = require('../assets/resources');
var utils = require('../utils/common');
var stringStream = require('../assets/stringStream');
var cssResolver = require('./css');
var range = require('../assets/range');
var cssEditTree = require('../editTree/css');
var editorUtils = require('../utils/editor');
var linearGradient = require('./gradient/linear');
var cssSyntaxes = ['css', 'less', 'sass', 'scss', 'stylus', 'styl'];
// XXX define preferences
prefs.define('css.gradient.prefixes', 'webkit, moz, o',
'A comma-separated list of vendor-prefixes for which values should '
+ 'be generated.');
prefs.define('css.gradient.oldWebkit', false,
'Generate gradient definition for old Webkit implementations');
prefs.define('css.gradient.omitDefaultDirection', true,
'Do not output default direction definition in generated gradients.');
prefs.define('css.gradient.defaultProperty', 'background-image',
'When gradient expanded outside CSS value context, it will produce '
+ 'properties with this name.');
prefs.define('css.gradient.fallback', false,
'With this option enabled, CSS gradient generator will produce '
+ 'background-color
property with gradient first color '
+ 'as fallback for old browsers.');
/**
* Resolves property name (abbreviation): searches for snippet definition in
* 'resources' and returns new name of matched property
*/
function resolvePropertyName(name, syntax) {
var snippet = resources.findSnippet(syntax, name);
if (!snippet && prefs.get('css.fuzzySearch')) {
var minScore = parseFloat(prefs.get('css.fuzzySearchMinScore'));
snippet = resources.fuzzyFindSnippet(syntax, name, minScore);
}
if (snippet) {
if (typeof snippet !== 'string') {
snippet = snippet.data;
}
return cssResolver.splitSnippet(snippet).name;
}
}
/**
* Returns vendor prefixes for given gradient type
* @param {String} type Gradient type (currently, 'linear-gradient'
* is the only supported value)
* @return {Array}
*/
function getGradientPrefixes(type) {
var prefixes = cssResolver.vendorPrefixes(type);
if (!prefixes) {
// disabled Can I Use, fallback to property list
prefixes = prefs.getArray('css.gradient.prefixes');
}
return prefixes || [];
}
function getPrefixedNames(type) {
var prefixes = getGradientPrefixes(type);
var names = prefixes
? prefixes.map(function(p) {
return '-' + p + '-' + type;
})
: [];
names.push(type);
return names;
}
/**
* Returns list of CSS properties with gradient
* @param {Array} gradient List of gradient objects
* @param {CSSEditElement} property Original CSS property
* @returns {Array}
*/
function getPropertiesForGradient(gradients, property) {
var props = [];
var propertyName = property.name();
var omitDir = prefs.get('css.gradient.omitDefaultDirection');
if (prefs.get('css.gradient.fallback') && ~propertyName.toLowerCase().indexOf('background')) {
props.push({
name: 'background-color',
value: '${1:' + gradients[0].gradient.colorStops[0].color + '}'
});
}
var value = property.value();
getGradientPrefixes('linear-gradient').forEach(function(prefix) {
var name = cssResolver.prefixed(propertyName, prefix);
if (prefix == 'webkit' && prefs.get('css.gradient.oldWebkit')) {
try {
props.push({
name: name,
value: insertGradientsIntoCSSValue(gradients, value, {
prefix: prefix,
oldWebkit: true,
omitDefaultDirection: omitDir
})
});
} catch(e) {}
}
props.push({
name: name,
value: insertGradientsIntoCSSValue(gradients, value, {
prefix: prefix,
omitDefaultDirection: omitDir
})
});
});
return props.sort(function(a, b) {
return b.name.length - a.name.length;
});
}
/**
* Replaces old gradient definitions in given CSS property value
* with new ones, preserving original formatting
* @param {Array} gradients List of CSS gradients
* @param {String} value Original CSS value
* @param {Object} options Options for gradient’s stringify() method
* @return {String}
*/
function insertGradientsIntoCSSValue(gradients, value, options) {
// gradients *should* passed in order they actually appear in CSS property
// iterate over it in backward direction to preserve gradient locations
options = options || {};
gradients = utils.clone(gradients);
gradients.reverse().forEach(function(item, i) {
var suffix = !i && options.placeholder ? options.placeholder : '';
var str = options.oldWebkit ? item.gradient.stringifyOldWebkit(options) : item.gradient.stringify(options);
value = utils.replaceSubstring(value, str + suffix, item.matchedPart);
});
return value;
}
/**
* Returns list of properties with the same meaning
* (e.g. vendor-prefixed + original name)
* @param {String} property CSS property name
* @return {Array}
*/
function similarPropertyNames(property) {
if (typeof property !== 'string') {
property = property.name();
}
var similarProps = (cssResolver.vendorPrefixes(property) || []).map(function(prefix) {
return '-' + prefix + '-' + property;
});
similarProps.push(property);
return similarProps;
}
/**
* Pastes gradient definition into CSS rule with correct vendor-prefixes
* @param {EditElement} property Matched CSS property
* @param {Array} gradients List of gradients to insert
*/
function pasteGradient(property, gradients) {
var rule = property.parent;
var alignVendor = prefs.get('css.alignVendor');
var omitDir = prefs.get('css.gradient.omitDefaultDirection');
// we may have aligned gradient definitions: find the smallest value
// separator
var sep = property.styleSeparator;
var before = property.styleBefore;
// first, remove all properties within CSS rule with the same name and
// gradient definition
rule.getAll(similarPropertyNames(property)).forEach(function(item) {
if (item != property && /gradient/i.test(item.value())) {
if (item.styleSeparator.length < sep.length) {
sep = item.styleSeparator;
}
if (item.styleBefore.length < before.length) {
before = item.styleBefore;
}
rule.remove(item);
}
});
if (alignVendor) {
// update prefix
if (before != property.styleBefore) {
var fullRange = property.fullRange();
rule._updateSource(before, fullRange.start, fullRange.start + property.styleBefore.length);
property.styleBefore = before;
}
// update separator value
if (sep != property.styleSeparator) {
rule._updateSource(sep, property.nameRange().end, property.valueRange().start);
property.styleSeparator = sep;
}
}
var value = property.value();
// create list of properties to insert
var propsToInsert = getPropertiesForGradient(gradients, property);
// align prefixed values
if (alignVendor) {
var names = [], values = [];
propsToInsert.forEach(function(item) {
names.push(item.name);
values.push(item.value);
});
values.push(property.value());
names.push(property.name());
var valuePads = utils.getStringsPads(values.map(function(v) {
return v.substring(0, v.indexOf('('));
}));
var namePads = utils.getStringsPads(names);
property.name(namePads[namePads.length - 1] + property.name());
propsToInsert.forEach(function(prop, i) {
prop.name = namePads[i] + prop.name;
prop.value = valuePads[i] + prop.value;
});
property.value(valuePads[valuePads.length - 1] + property.value());
}
// put vendor-prefixed definitions before current rule
propsToInsert.forEach(function(prop) {
rule.add(prop.name, prop.value, rule.indexOf(property));
});
// put vanilla-clean gradient definition into current rule
property.value(insertGradientsIntoCSSValue(gradients, value, {
placeholder: '${2}',
omitDefaultDirection: omitDir
}));
}
/**
* Validates caret position relatively to located gradients
* in CSS rule. In other words, it checks if it’s safe to
* expand gradients for current caret position or not.
*
* See issue https://github.com/sergeche/emmet-sublime/issues/411
*
* @param {Array} gradients List of parsed gradients
* @param {Number} caretPos Current caret position
* @param {String} syntax Current document syntax
* @return {Boolean}
*/
function isValidCaretPosition(gradients, caretPos, syntax) {
syntax = syntax || 'css';
if (syntax == 'css' || syntax == 'less' || syntax == 'scss') {
return true;
}
var offset = gradients.property.valueRange(true).start;
var parts = gradients.gradients;
// in case of preprocessors where properties are separated with
// newlines, make sure there’s no gradient definition past
// current caret position.
for (var i = parts.length - 1; i >= 0; i--) {
if (parts[i].matchedPart.start + offset >= caretPos) {
return false;
}
}
return true;
}
module = module || {};
return module.exports = {
/**
* Search for gradient definitions inside CSS property value
* @returns {Array} Array of matched gradients
*/
findGradients: function(cssProp) {
var value = cssProp.value();
var gradients = [];
var that = this;
cssProp.valueParts().forEach(function(part) {
var partValue = part.substring(value);
if (linearGradient.isLinearGradient(partValue)) {
var gradient = linearGradient.parse(partValue);
if (gradient) {
gradients.push({
gradient: gradient,
matchedPart: part
});
}
}
});
return gradients.length ? gradients : null;
},
/**
* Returns list of gradients found in CSS property
* of given CSS code in specified (caret) position
* @param {String} css CSS code snippet
* @param {Number} pos Character index where to start searching for CSS property
* @return {Array}
*/
gradientsFromCSSProperty: function(css, pos) {
var cssProp = cssEditTree.propertyFromPosition(css, pos);
if (cssProp) {
var grd = this.findGradients(cssProp);
if (grd) {
return {
property: cssProp,
gradients: grd
};
}
}
return null;
},
/**
* Handler for “Expand Abbreviation” action
* @param {IEmmetEditor} editor
* @param {String} syntax
* @param {String} profile
* return {Boolean}
*/
expandAbbreviationHandler: function(editor, syntax, profile) {
var info = editorUtils.outputInfo(editor, syntax, profile);
if (!~cssSyntaxes.indexOf(info.syntax)) {
return false;
}
// let's see if we are expanding gradient definition
var caret = editor.getCaretPos();
var content = info.content;
var gradients = this.gradientsFromCSSProperty(content, caret);
if (gradients) {
if (!isValidCaretPosition(gradients, caret, info.syntax)) {
return false;
}
var cssProperty = gradients.property;
var cssRule = cssProperty.parent;
var ruleStart = cssRule.options.offset || 0;
var ruleEnd = ruleStart + cssRule.toString().length;
// Handle special case:
// user wrote gradient definition between existing CSS
// properties and did not finished it with semicolon.
// In this case, we have semicolon right after gradient
// definition and re-parse rule again
if (/[\n\r]/.test(cssProperty.value())) {
// insert semicolon at the end of gradient definition
var insertPos = cssProperty.valueRange(true).start + utils.last(gradients.gradients).matchedPart.end;
content = utils.replaceSubstring(content, ';', insertPos);
var _gradients = this.gradientsFromCSSProperty(content, caret);
if (_gradients) {
gradients = _gradients;
cssProperty = gradients.property;
cssRule = cssProperty.parent;
}
}
// make sure current property has terminating semicolon
cssProperty.end(';');
// resolve CSS property name
var resolvedName = resolvePropertyName(cssProperty.name(), syntax);
if (resolvedName) {
cssProperty.name(resolvedName);
}
pasteGradient(cssProperty, gradients.gradients);
editor.replaceContent(cssRule.toString(), ruleStart, ruleEnd, true);
return true;
}
return this.expandGradientOutsideValue(editor, syntax);
},
/**
* Tries to expand gradient outside CSS value
* @param {IEmmetEditor} editor
* @param {String} syntax
*/
expandGradientOutsideValue: function(editor, syntax) {
var propertyName = prefs.get('css.gradient.defaultProperty');
var omitDir = prefs.get('css.gradient.omitDefaultDirection');
if (!propertyName) {
return false;
}
// assuming that gradient definition is written on new line,
// do a simplified parsing
var content = String(editor.getContent());
/** @type Range */
var lineRange = range.create(editor.getCurrentLineRange());
// get line content and adjust range with padding
var line = lineRange.substring(content)
.replace(/^\s+/, function(pad) {
lineRange.start += pad.length;
return '';
})
.replace(/\s+$/, function(pad) {
lineRange.end -= pad.length;
return '';
});
// trick parser: make it think that we’re parsing actual CSS property
var fakeCSS = 'a{' + propertyName + ': ' + line + ';}';
var gradients = this.gradientsFromCSSProperty(fakeCSS, fakeCSS.length - 2);
if (gradients) {
var props = getPropertiesForGradient(gradients.gradients, gradients.property);
props.push({
name: gradients.property.name(),
value: insertGradientsIntoCSSValue(gradients.gradients, gradients.property.value(), {
placeholder: '${2}',
omitDefaultDirection: omitDir
})
});
var sep = cssResolver.getSyntaxPreference('valueSeparator', syntax);
var end = cssResolver.getSyntaxPreference('propertyEnd', syntax);
if (prefs.get('css.alignVendor')) {
var pads = utils.getStringsPads(props.map(function(prop) {
return prop.value.substring(0, prop.value.indexOf('('));
}));
props.forEach(function(prop, i) {
prop.value = pads[i] + prop.value;
});
}
props = props.map(function(item) {
return item.name + sep + item.value + end;
});
editor.replaceContent(props.join('\n'), lineRange.start, lineRange.end);
return true;
}
return false;
},
/**
* Handler for “Reflect CSS Value“ action
* @param {String} property
*/
reflectValueHandler: function(property) {
var omitDir = prefs.get('css.gradient.omitDefaultDirection');
var gradients = this.findGradients(property);
if (!gradients) {
return false;
}
var that = this;
var value = property.value();
// reflect value for properties with the same name
property.parent.getAll(similarPropertyNames(property)).forEach(function(prop) {
if (prop === property) {
return;
}
// make sure current property contains gradient definition,
// otherwise – skip it
var localGradients = that.findGradients(prop);
if (localGradients) {
// detect vendor prefix for current property
var localValue = prop.value();
var dfn = localGradients[0].matchedPart.substring(localValue);
var prefix = '';
if (/^\s*\-([a-z]+)\-/.test(dfn)) {
prefix = RegExp.$1;
}
prop.value(insertGradientsIntoCSSValue(gradients, value, {
prefix: prefix,
omitDefaultDirection: omitDir
}));
}
});
return true;
}
};
});
},{"../assets/preferences":"assets/preferences.js","../assets/range":"assets/range.js","../assets/resources":"assets/resources.js","../assets/stringStream":"assets/stringStream.js","../editTree/css":"editTree/css.js","../utils/common":"utils/common.js","../utils/editor":"utils/editor.js","./css":"resolver/css.js","./gradient/linear":"resolver/gradient/linear.js"}],"resolver/gradient/linear.js":[function(require,module,exports){
/**
* CSS linear gradient definition
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var stringStream = require('../../assets/stringStream');
var utils = require('../../utils/common');
// all directions are expressed in “new style” degrees
var directions = {
'bottom': 0,
'bottom left': 45,
'left': 90,
'top left': 135,
'top': 180,
'top right': 225,
'right': 270,
'bottom right': 315,
'to top': 0,
'to top right': 45,
'to right': 90,
'to bottom right': 135,
'to bottom': 180,
'to bottom left': 225,
'to left': 270,
'to top left': 315
};
var defaultDirections = ['top', 'to bottom', '0deg'];
var reLinearGradient = /^\s*(\-[a-z]+\-)?(lg|linear\-gradient)\s*\(/i;
var reDeg = /(\d+)deg/i;
var reKeyword = /top|bottom|left|right/i;
function LinearGradient(dfn) {
this.colorStops = [];
this.direction = 180;
// extract tokens
var stream = stringStream.create(utils.trim(dfn));
var ch, cur;
while ((ch = stream.next())) {
if (stream.peek() == ',') {
// Is it a first entry? Check if it’s a direction
cur = stream.current();
if (!this.colorStops.length && (reDeg.test(cur) || reKeyword.test(cur))) {
this.direction = resolveDirection(cur);
} else {
this.addColorStop(cur);
}
stream.next();
stream.eatSpace();
stream.start = stream.pos;
} else if (ch == '(') { // color definition, like 'rgb(0,0,0)'
stream.skipTo(')');
}
}
// add last token
this.addColorStop(stream.current());
}
LinearGradient.prototype = {
type: 'linear-gradient',
addColorStop: function(color, ix) {
color = normalizeSpace(color || '');
if (!color) {
return;
}
color = this.parseColorStop(color);
if (typeof ix === 'undefined') {
this.colorStops.push(color);
} else {
this.colorStops.splice(ix, 0, color);
}
},
/**
* Parses color stop definition
* @param {String} colorStop
* @returns {Object}
*/
parseColorStop: function(colorStop) {
colorStop = normalizeSpace(colorStop);
// find color declaration
// first, try complex color declaration, like rgb(0,0,0)
var color = null;
colorStop = colorStop.replace(/^(\w+\(.+?\))\s*/, function(str, c) {
color = c;
return '';
});
if (!color) {
// try simple declaration, like yellow, #fco, #ffffff, etc.
var parts = colorStop.split(' ');
color = parts[0];
colorStop = parts[1] || '';
}
var result = {
color: color
};
if (colorStop) {
// there's position in color stop definition
colorStop.replace(/^(\-?[\d\.]+)([a-z%]+)?$/, function(str, pos, unit) {
result.position = pos;
if (~pos.indexOf('.')) {
unit = '';
} else if (!unit) {
unit = '%';
}
if (unit) {
result.unit = unit;
}
});
}
return result;
},
stringify: function(options) {
options = options || {};
var fn = 'linear-gradient';
if (options.prefix) {
fn = '-' + options.prefix + '-' + fn;
}
// transform color-stops
var parts = this.colorStops.map(function(cs) {
var pos = cs.position ? ' ' + cs.position + (cs.unit || '') : '';
return cs.color + pos;
});
var dir = stringifyDirection(this.direction, !!options.prefix);
if (!options.omitDefaultDirection || !~defaultDirections.indexOf(dir)) {
parts.unshift(dir);
}
return fn + '(' + parts.join(', ') + ')';
},
stringifyOldWebkit: function() {
var colorStops = this.colorStops.map(function(item) {
return utils.clone(item);
});
// normalize color-stops position
colorStops.forEach(function(cs) {
if (!('position' in cs)) // implied position
return;
if (~cs.position.indexOf('.') || cs.unit == '%') {
cs.position = parseFloat(cs.position) / (cs.unit == '%' ? 100 : 1);
} else {
throw "Can't convert color stop '" + (cs.position + (cs.unit || '')) + "'";
}
});
this._fillImpliedPositions(colorStops);
// transform color-stops into string representation
colorStops = colorStops.map(function(cs, i) {
if (!cs.position && !i) {
return 'from(' + cs.color + ')';
}
if (cs.position == 1 && i == colorStops.length - 1) {
return 'to(' + cs.color + ')';
}
return 'color-stop(' + (cs.position.toFixed(2).replace(/\.?0+$/, '')) + ', ' + cs.color + ')';
});
return '-webkit-gradient(linear, '
+ oldWebkitDirection((this.direction + 180) % 360)
+ ', '
+ colorStops.join(', ')
+ ')';
},
/**
* Fills-out implied positions in color-stops. This function is useful for
* old Webkit gradient definitions
*/
_fillImpliedPositions: function(colorStops) {
var from = 0;
colorStops.forEach(function(cs, i) {
// make sure that first and last positions are defined
if (!i) {
return cs.position = cs.position || 0;
}
if (i == colorStops.length - 1 && !('position' in cs)) {
cs.position = 1;
}
if ('position' in cs) {
var start = colorStops[from].position || 0;
var step = (cs.position - start) / (i - from);
colorStops.slice(from, i).forEach(function(cs2, j) {
cs2.position = start + step * j;
});
from = i;
}
});
},
valueOf: function() {
return this.stringify();
}
};
function normalizeSpace(str) {
return utils.trim(str).replace(/\s+/g, ' ');
}
/**
* Resolves textual direction to degrees
* @param {String} dir Direction to resolve
* @return {Number}
*/
function resolveDirection(dir) {
if (typeof dir == 'number') {
return dir;
}
dir = normalizeSpace(dir).toLowerCase();
if (reDeg.test(dir)) {
return +RegExp.$1;
}
var prefix = /^to\s/.test(dir) ? 'to ' : '';
var left = ~dir.indexOf('left') && 'left';
var right = ~dir.indexOf('right') && 'right';
var top = ~dir.indexOf('top') && 'top';
var bottom = ~dir.indexOf('bottom') && 'bottom';
var key = normalizeSpace(prefix + (top || bottom || '') + ' ' + (left || right || ''));
return directions[key] || 0;
}
/**
* Tries to find keyword for given direction, expressed in degrees
* @param {Number} dir Direction (degrees)
* @param {Boolean} oldStyle Use old style keywords (e.g. "top" instead of "to bottom")
* @return {String} Keyword or Ndeg
expression
*/
function stringifyDirection(dir, oldStyle) {
var reNewStyle = /^to\s/;
var keys = Object.keys(directions).filter(function(k) {
var hasPrefix = reNewStyle.test(k);
return oldStyle ? !hasPrefix : hasPrefix;
});
for (var i = 0; i < keys.length; i++) {
if (directions[keys[i]] == dir) {
return keys[i];
}
}
if (oldStyle) {
dir = (dir + 270) % 360;
}
return dir + 'deg';
}
/**
* Creates direction definition for old Webkit gradients
* @param {String} direction
* @returns {String}
*/
function oldWebkitDirection(dir) {
dir = stringifyDirection(dir, true);
if(reDeg.test(dir)) {
throw "The direction is an angle that can’t be converted.";
}
var v = function(pos) {
return ~dir.indexOf(pos) ? '100%' : '0';
};
return v('left') + ' ' + v('top') + ', ' + v('right') + ' ' + v('bottom');
}
return {
/**
* Parses gradient definition into an object.
* This object can be used to transform gradient into various
* forms
* @param {String} gradient Gradient definition
* @return {LinearGradient}
*/
parse: function(gradient) {
// cut out all redundant data
if (this.isLinearGradient(gradient)) {
gradient = gradient.replace(/^\s*[\-a-z]+\s*\(|\)\s*$/ig, '');
} else {
throw 'Invalid linear gradient definition:\n' + gradient;
}
return new LinearGradient(gradient);
},
/**
* Check if given string can be parsed as linear gradient
* @param {String} str
* @return {Boolean}
*/
isLinearGradient: function(str) {
return reLinearGradient.test(str);
},
resolveDirection: resolveDirection,
stringifyDirection: stringifyDirection
};
});
},{"../../assets/stringStream":"assets/stringStream.js","../../utils/common":"utils/common.js"}],"resolver/tagName.js":[function(require,module,exports){
/**
* Module for resolving tag names: returns best matched tag name for child
* element based on passed parent's tag name. Also provides utility function
* for element type detection (inline, block-level, empty)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
var elementTypes = {
// empty: 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command'.split(','),
empty: [],
blockLevel: 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6'.split(','),
inlineLevel: 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'.split(',')
};
var elementMap = {
'p': 'span',
'ul': 'li',
'ol': 'li',
'table': 'tr',
'tr': 'td',
'tbody': 'tr',
'thead': 'tr',
'tfoot': 'tr',
'colgroup': 'col',
'select': 'option',
'optgroup': 'option',
'audio': 'source',
'video': 'source',
'object': 'param',
'map': 'area'
};
return {
/**
* Returns best matched child element name for passed parent's
* tag name
* @param {String} name
* @returns {String}
* @memberOf tagName
*/
resolve: function(name) {
name = (name || '').toLowerCase();
if (name in elementMap)
return this.getMapping(name);
if (this.isInlineLevel(name))
return 'span';
return 'div';
},
/**
* Returns mapped child element name for passed parent's name
* @param {String} name
* @returns {String}
*/
getMapping: function(name) {
return elementMap[name.toLowerCase()];
},
/**
* Check if passed element name belongs to inline-level element
* @param {String} name
* @returns {Boolean}
*/
isInlineLevel: function(name) {
return this.isTypeOf(name, 'inlineLevel');
},
/**
* Check if passed element belongs to block-level element.
* For better matching of unknown elements (for XML, for example),
* you should use !this.isInlineLevel(name)
* @returns {Boolean}
*/
isBlockLevel: function(name) {
return this.isTypeOf(name, 'blockLevel');
},
/**
* Check if passed element is void (i.e. should not have closing tag).
* @returns {Boolean}
*/
isEmptyElement: function(name) {
return this.isTypeOf(name, 'empty');
},
/**
* Generic function for testing if element name belongs to specified
* elements collection
* @param {String} name Element name
* @param {String} type Collection name
* @returns {Boolean}
*/
isTypeOf: function(name, type) {
return ~elementTypes[type].indexOf(name);
},
/**
* Adds new parent–child mapping
* @param {String} parent
* @param {String} child
*/
addMapping: function(parent, child) {
elementMap[parent] = child;
},
/**
* Removes parent-child mapping
*/
removeMapping: function(parent) {
if (parent in elementMap)
delete elementMap[parent];
},
/**
* Adds new element into collection
* @param {String} name Element name
* @param {String} collection Collection name
*/
addElementToCollection: function(name, collection) {
if (!elementTypes[collection])
elementTypes[collection] = [];
var col = this.getCollection(collection);
if (!~col.indexOf(name)) {
col.push(name);
}
},
/**
* Removes element name from specified collection
* @param {String} name Element name
* @param {String} collection Collection name
* @returns
*/
removeElementFromCollection: function(name, collection) {
if (collection in elementTypes) {
elementTypes[collection] = utils.without(this.getCollection(collection), name);
}
},
/**
* Returns elements name collection
* @param {String} name Collection name
* @returns {Array}
*/
getCollection: function(name) {
return elementTypes[name];
}
};
});
},{"../utils/common":"utils/common.js"}],"utils/abbreviation.js":[function(require,module,exports){
/**
* Utility functions to work with AbbreviationNode
as HTML element
* @param {Function} require
* @param {Underscore} _
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var elements = require('../assets/elements');
var tabStops = require('../assets/tabStops');
var utils = require('../utils/common');
var tagName = require('../resolver/tagName');
return {
/**
* Test if passed node is unary (no closing tag)
* @param {AbbreviationNode} node
* @return {Boolean}
*/
isUnary: function(node) {
if (node.children.length || node._text || this.isSnippet(node)) {
return false;
}
var r = node.data('resource');
return r && r.is_empty;
},
/**
* Test if passed node is inline-level (like <strong>, <img>)
* @param {AbbreviationNode} node
* @return {Boolean}
*/
isInline: function(node) {
return node.isTextNode()
|| !node.name()
|| tagName.isInlineLevel(node.name());
},
/**
* Test if passed node is block-level
* @param {AbbreviationNode} node
* @return {Boolean}
*/
isBlock: function(node) {
return this.isSnippet(node) || !this.isInline(node);
},
/**
* Test if given node is a snippet
* @param {AbbreviationNode} node
* @return {Boolean}
*/
isSnippet: function(node) {
return elements.is(node.data('resource'), 'snippet');
},
/**
* This function tests if passed node content contains HTML tags.
* This function is mostly used for output formatting
* @param {AbbreviationNode} node
* @returns {Boolean}
*/
hasTagsInContent: function(node) {
return utils.matchesTag(node.content);
},
/**
* Test if current element contains block-level children
* @param {AbbreviationNode} node
* @return {Boolean}
*/
hasBlockChildren: function(node) {
return (this.hasTagsInContent(node) && this.isBlock(node))
|| node.children.some(function(child) {
return this.isBlock(child);
}, this);
},
/**
* Utility function that inserts content instead of ${child}
* variables on text
* @param {String} text Text where child content should be inserted
* @param {String} childContent Content to insert
* @param {Object} options
* @returns {String
*/
insertChildContent: function(text, childContent, options) {
options = utils.extend({
keepVariable: true,
appendIfNoChild: true
}, options || {});
var childVariableReplaced = false;
text = tabStops.replaceVariables(text, function(variable, name, data) {
var output = variable;
if (name == 'child') {
// add correct indentation
output = utils.padString(childContent, utils.getLinePaddingFromPosition(text, data.start));
childVariableReplaced = true;
if (options.keepVariable)
output += variable;
}
return output;
});
if (!childVariableReplaced && options.appendIfNoChild) {
text += childContent;
}
return text;
}
};
});
},{"../assets/elements":"assets/elements.js","../assets/tabStops":"assets/tabStops.js","../resolver/tagName":"resolver/tagName.js","../utils/common":"utils/common.js"}],"utils/action.js":[function(require,module,exports){
/**
* Utility methods for Emmet actions
* @author Sergey Chikuyonok (serge.che@gmail.com)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('./common');
var cssSections = require('./cssSections');
var abbreviationParser = require('../parser/abbreviation');
var htmlMatcher = require('../assets/htmlMatcher');
var xmlEditTree = require('../editTree/xml');
var range = require('../assets/range');
var resources = require('../assets/resources');
return {
mimeTypes: {
'gif' : 'image/gif',
'png' : 'image/png',
'jpg' : 'image/jpeg',
'jpeg': 'image/jpeg',
'svg' : 'image/svg+xml',
'html': 'text/html',
'htm' : 'text/html'
},
/**
* Extracts abbreviations from text stream, starting from the end
* @param {String} str
* @return {String} Abbreviation or empty string
* @memberOf emmet.actionUtils
*/
extractAbbreviation: function(str) {
var curOffset = str.length;
var startIndex = -1;
var groupCount = 0;
var braceCount = 0;
var textCount = 0;
while (true) {
curOffset--;
if (curOffset < 0) {
// moved to the beginning of the line
startIndex = 0;
break;
}
var ch = str.charAt(curOffset);
if (ch == ']') {
braceCount++;
} else if (ch == '[') {
if (!braceCount) { // unexpected brace
startIndex = curOffset + 1;
break;
}
braceCount--;
} else if (ch == '}') {
textCount++;
} else if (ch == '{') {
if (!textCount) { // unexpected brace
startIndex = curOffset + 1;
break;
}
textCount--;
} else if (ch == ')') {
groupCount++;
} else if (ch == '(') {
if (!groupCount) { // unexpected brace
startIndex = curOffset + 1;
break;
}
groupCount--;
} else {
if (braceCount || textCount)
// respect all characters inside attribute sets or text nodes
continue;
else if (!abbreviationParser.isAllowedChar(ch) || (ch == '>' && utils.endsWithTag(str.substring(0, curOffset + 1)))) {
// found stop symbol
startIndex = curOffset + 1;
break;
}
}
}
if (startIndex != -1 && !textCount && !braceCount && !groupCount)
// found something, remove some invalid symbols from the
// beginning and return abbreviation
return str.substring(startIndex).replace(/^[\*\+\>\^]+/, '');
else
return '';
},
/**
* Gets image size from image byte stream.
* @author http://romeda.org/rePublish/
* @param {String} stream Image byte stream (use IEmmetFile.read()
)
* @return {Object} Object with width
and height
properties
*/
getImageSize: function(stream) {
var pngMagicNum = "\211PNG\r\n\032\n",
jpgMagicNum = "\377\330",
gifMagicNum = "GIF8",
pos = 0,
nextByte = function() {
return stream.charCodeAt(pos++);
};
if (stream.substr(0, 8) === pngMagicNum) {
// PNG. Easy peasy.
pos = stream.indexOf('IHDR') + 4;
return {
width: (nextByte() << 24) | (nextByte() << 16) | (nextByte() << 8) | nextByte(),
height: (nextByte() << 24) | (nextByte() << 16) | (nextByte() << 8) | nextByte()
};
} else if (stream.substr(0, 4) === gifMagicNum) {
pos = 6;
return {
width: nextByte() | (nextByte() << 8),
height: nextByte() | (nextByte() << 8)
};
} else if (stream.substr(0, 2) === jpgMagicNum) {
pos = 2;
var l = stream.length;
while (pos < l) {
if (nextByte() != 0xFF) return;
var marker = nextByte();
if (marker == 0xDA) break;
var size = (nextByte() << 8) | nextByte();
if (marker >= 0xC0 && marker <= 0xCF && !(marker & 0x4) && !(marker & 0x8)) {
pos += 1;
return {
height: (nextByte() << 8) | nextByte(),
width: (nextByte() << 8) | nextByte()
};
} else {
pos += size - 2;
}
}
}
},
/**
* Captures context XHTML element from editor under current caret position.
* This node can be used as a helper for abbreviation extraction
* @param {IEmmetEditor} editor
* @returns {Object}
*/
captureContext: function(editor, pos) {
var allowedSyntaxes = {'html': 1, 'xml': 1, 'xsl': 1};
var syntax = editor.getSyntax();
if (syntax in allowedSyntaxes) {
var content = editor.getContent();
if (typeof pos === 'undefined') {
pos = editor.getCaretPos();
}
var tag = htmlMatcher.find(content, pos);
if (tag && tag.type == 'tag') {
var startTag = tag.open;
var contextNode = {
name: startTag.name,
attributes: [],
match: tag
};
// parse attributes
var tagTree = xmlEditTree.parse(startTag.range.substring(content));
if (tagTree) {
contextNode.attributes = tagTree.getAll().map(function(item) {
return {
name: item.name(),
value: item.value()
};
});
}
return contextNode;
}
}
return null;
},
/**
* Find expression bounds in current editor at caret position.
* On each character a fn
function will be called and must
* return true
if current character meets requirements,
* false
otherwise
* @param {IEmmetEditor} editor
* @param {Function} fn Function to test each character of expression
* @return {Range}
*/
findExpressionBounds: function(editor, fn) {
var content = String(editor.getContent());
var il = content.length;
var exprStart = editor.getCaretPos() - 1;
var exprEnd = exprStart + 1;
// start by searching left
while (exprStart >= 0 && fn(content.charAt(exprStart), exprStart, content)) exprStart--;
// then search right
while (exprEnd < il && fn(content.charAt(exprEnd), exprEnd, content)) exprEnd++;
if (exprEnd > exprStart) {
return range([++exprStart, exprEnd]);
}
},
/**
* @param {IEmmetEditor} editor
* @param {Object} data
* @returns {Boolean}
*/
compoundUpdate: function(editor, data) {
if (data) {
var sel = editor.getSelectionRange();
editor.replaceContent(data.data, data.start, data.end, true);
editor.createSelection(data.caret, data.caret + sel.end - sel.start);
return true;
}
return false;
},
/**
* Common syntax detection method for editors that doesn’t provide any
* info about current syntax scope.
* @param {IEmmetEditor} editor Current editor
* @param {String} hint Any syntax hint that editor can provide
* for syntax detection. Default is 'html'
* @returns {String}
*/
detectSyntax: function(editor, hint) {
var syntax = hint || 'html';
if (!resources.hasSyntax(syntax)) {
syntax = 'html';
}
if (syntax == 'html' && (this.isStyle(editor) || this.isInlineCSS(editor))) {
syntax = 'css';
}
if (syntax == 'styl') {
syntax = 'stylus';
}
return syntax;
},
/**
* Common method for detecting output profile
* @param {IEmmetEditor} editor
* @returns {String}
*/
detectProfile: function(editor) {
var syntax = editor.getSyntax();
// get profile from syntax definition
var profile = resources.findItem(syntax, 'profile');
if (profile) {
return profile;
}
switch(syntax) {
case 'xml':
case 'xsl':
return 'xml';
case 'css':
if (this.isInlineCSS(editor)) {
return 'line';
}
break;
case 'html':
profile = resources.getVariable('profile');
if (!profile) { // no forced profile, guess from content
// html or xhtml?
profile = this.isXHTML(editor) ? 'xhtml': 'html';
}
return profile;
}
return 'xhtml';
},
/**
* Tries to detect if current document is XHTML one.
* @param {IEmmetEditor} editor
* @returns {Boolean}
*/
isXHTML: function(editor) {
return editor.getContent().search(/]+XHTML/i) != -1;
},
/**
* Check if current caret position is inside <style> tag
* @param {IEmmetEditor} editor
* @returns {Range} Inner range of <style> tag
*/
isStyle: function(editor) {
return !!cssSections.styleTagRange(editor.getContent(), editor.getCaretPos());
},
/**
* Check if given CSS dialect is supported by CSS actions
* @param {String} syntax
* @return {Boolean}
*/
isSupportedCSS: function(syntax) {
return syntax == 'css' || syntax == 'less' || syntax == 'scss';
},
/**
* Check if current caret position is inside "style" attribute of HTML
* element
* @param {IEmmetEditor} editor
* @returns {Range} Inner range of style attribute
*/
isInlineCSS: function(editor) {
return !!cssSections.styleAttrRange(editor.getContent(), editor.getCaretPos());
}
};
});
},{"../assets/htmlMatcher":"assets/htmlMatcher.js","../assets/range":"assets/range.js","../assets/resources":"assets/resources.js","../editTree/xml":"editTree/xml.js","../parser/abbreviation":"parser/abbreviation.js","./common":"utils/common.js","./cssSections":"utils/cssSections.js"}],"utils/base64.js":[function(require,module,exports){
/**
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
return {
/**
* Encodes data using base64 algorithm
* @author Tyler Akins (http://rumkin.com)
* @param {String} input
* @returns {String}
*/
encode : function(input) {
var output = [];
var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
var i = 0, il = input.length, b64 = chars;
while (i < il) {
cdp1 = input.charCodeAt(i++);
cdp2 = input.charCodeAt(i++);
cdp3 = input.charCodeAt(i++);
chr1 = cdp1 & 0xff;
chr2 = cdp2 & 0xff;
chr3 = cdp3 & 0xff;
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(cdp2)) {
enc3 = enc4 = 64;
} else if (isNaN(cdp3)) {
enc4 = 64;
}
output.push(b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4));
}
return output.join('');
},
/**
* Decodes string using MIME base64 algorithm
*
* @author Tyler Akins (http://rumkin.com)
* @param {String} data
* @return {String}
*/
decode : function(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmpArr = [];
var b64 = chars, il = data.length;
if (!data) {
return data;
}
data += '';
do { // unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 == 64) {
tmpArr[ac++] = String.fromCharCode(o1);
} else if (h4 == 64) {
tmpArr[ac++] = String.fromCharCode(o1, o2);
} else {
tmpArr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < il);
return tmpArr.join('');
}
};
});
},{}],"utils/comments.js":[function(require,module,exports){
/**
* Utility module for working with comments in source code
* (mostly stripping it from source)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('./common');
var range = require('../assets/range');
var stringStream = require('../assets/stringStream');
var reHasComment = /\/\*|\/\//;
return {
/**
* Replaces all comments in given CSS source with spaces,
* which allows more reliable (and faster) token search
* in CSS content
* @param {String} content CSS content
* @return {String}
*/
strip: function(content) {
if (!reHasComment.test(content)) {
return content;
}
var stream = stringStream(content);
var replaceRanges = [];
var ch, ch2;
while ((ch = stream.next())) {
if (ch === '/') {
ch2 = stream.peek();
if (ch2 === '*') { // multiline CSS comment
stream.start = stream.pos - 1;
if (stream.skipTo('*/')) {
stream.pos += 2;
} else {
// unclosed comment
stream.skipToEnd();
}
replaceRanges.push([stream.start, stream.pos]);
} else if (ch2 === '/') {
// preprocessor’s single line comments
stream.start = stream.pos - 1;
while ((ch2 = stream.next())) {
if (ch2 === '\n' || ch2 == '\r') {
break
}
}
replaceRanges.push([stream.start, stream.pos]);
}
} else {
stream.skipQuoted();
}
}
return utils.replaceWith(content, replaceRanges, ' ');
}
};
});
},{"../assets/range":"assets/range.js","../assets/stringStream":"assets/stringStream.js","./common":"utils/common.js"}],"utils/common.js":[function(require,module,exports){
/**
* Common utility helper for Emmet
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var range = require('../assets/range');
/**
* Special token used as a placeholder for caret positions inside
* generated output
*/
var caretPlaceholder = '${0}';
return {
reTag: /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/,
defaultSyntax: function() {
return 'html';
},
defaultProfile: function() {
return 'plain';
},
/**
* Test if passed string ends with XHTML tag. This method is used for testing
* '>' character: it belongs to tag or it's a part of abbreviation?
* @param {String} str
* @return {Boolean}
*/
endsWithTag: function(str) {
return this.reTag.test(str);
},
/**
* Check if passed symbol is a number
* @param {String} ch
* @returns {Boolean}
*/
isNumeric: function(ch) {
if (typeof(ch) == 'string')
ch = ch.charCodeAt(0);
return (ch && ch > 47 && ch < 58);
},
/**
* Trim whitespace from string
* @param {String} text
* @return {String}
*/
trim: (function() {
if (String.prototype.trim) {
return function(text) {
return text ? text.trim() : '';
};
}
return function(text) {
return (text || "").replace(/^\s+|\s+$/g, "");
}
})(),
/**
* Split text into lines. Set remove_empty
to true to filter
* empty lines
* @param {String} text Text to split
* @param {Boolean} removeEmpty Remove empty lines from result
* @return {Array}
*/
splitByLines: function(text, removeEmpty) {
// IE fails to split string by regexp,
// need to normalize newlines first
// Also, Mozilla's Rhiho JS engine has a weird newline bug
var nl = '\n';
var lines = (text || '')
.replace(/\r\n/g, '\n')
.replace(/\n\r/g, '\n')
.replace(/\r/g, '\n')
.replace(/\n/g, nl)
.split(nl);
if (removeEmpty) {
lines = lines.filter(function(line) {
return line.length && !!this.trim(line);
}, this);
}
return lines;
},
/**
* Repeats string howMany
times
* @param {String} str
* @param {Number} how_many
* @return {String}
*/
repeatString: function(str, howMany) {
var out = '';
while (howMany--) {
out += str;
}
return out;
},
/**
* Returns list of paddings that should be used to align passed string
* @param {Array} strings
* @returns {Array}
*/
getStringsPads: function(strings) {
var lengths = strings.map(function(s) {
return typeof s === 'string' ? s.length : +s;
});
var max = lengths.reduce(function(prev, cur) {
return typeof prev === 'undefined' ? cur : Math.max(prev, cur);
});
return lengths.map(function(l) {
var pad = max - l;
return pad ? this.repeatString(' ', pad) : '';
}, this);
},
/**
* Indents text with padding
* @param {String} text Text to indent
* @param {String} pad Padding size (number) or padding itself (string)
* @return {String}
*/
padString: function(text, pad) {
var result = [];
var lines = this.splitByLines(text);
var nl = '\n';
result.push(lines[0]);
for (var j = 1; j < lines.length; j++)
result.push(nl + pad + lines[j]);
return result.join('');
},
/**
* Pad string with zeroes
* @param {String} str String to pad
* @param {Number} pad Desired string length
* @return {String}
*/
zeroPadString: function(str, pad) {
var padding = '';
var il = str.length;
while (pad > il++) padding += '0';
return padding + str;
},
/**
* Removes padding at the beginning of each text's line
* @param {String} text
* @param {String} pad
*/
unindentString: function(text, pad) {
var lines = this.splitByLines(text);
var pl = pad.length;
for (var i = 0, il = lines.length, line; i < il; i++) {
line = lines[i];
if (line.substr(0, pl) === pad) {
lines[i] = line.substr(pl);
}
}
return lines.join('\n');
},
/**
* Replaces unescaped symbols in str
. For example, the '$' symbol
* will be replaced in 'item$count', but not in 'item\$count'.
* @param {String} str Original string
* @param {String} symbol Symbol to replace
* @param {String} replace Symbol replacement. Might be a function that
* returns new value
* @return {String}
*/
replaceUnescapedSymbol: function(str, symbol, replace) {
var i = 0;
var il = str.length;
var sl = symbol.length;
var matchCount = 0;
while (i < il) {
if (str.charAt(i) == '\\') {
// escaped symbol, skip next character
i += sl + 1;
} else if (str.substr(i, sl) == symbol) {
// have match
var curSl = sl;
matchCount++;
var newValue = replace;
if (typeof replace === 'function') {
var replaceData = replace(str, symbol, i, matchCount);
if (replaceData) {
curSl = replaceData[0].length;
newValue = replaceData[1];
} else {
newValue = false;
}
}
if (newValue === false) { // skip replacement
i++;
continue;
}
str = str.substring(0, i) + newValue + str.substring(i + curSl);
// adjust indexes
il = str.length;
i += newValue.length;
} else {
i++;
}
}
return str;
},
/**
* Replaces '$' character in string assuming it might be escaped with '\'
* @param {String} str String where character should be replaced
* @param {String} value New value
* @return {String}
*/
replaceCounter: function(str, value, total) {
var symbol = '$';
// in case we received strings from Java, convert the to native strings
str = String(str);
value = String(value);
if (/^\-?\d+$/.test(value)) {
value = +value;
}
var that = this;
return this.replaceUnescapedSymbol(str, symbol, function(str, symbol, pos, matchNum){
if (str.charAt(pos + 1) == '{' || that.isNumeric(str.charAt(pos + 1)) ) {
// it's a variable, skip it
return false;
}
// replace sequense of $ symbols with padded number
var j = pos + 1;
while(str.charAt(j) == '$' && str.charAt(j + 1) != '{') j++;
var pad = j - pos;
// get counter base
var base = 0, decrement = false, m;
if ((m = str.substr(j).match(/^@(\-?)(\d*)/))) {
j += m[0].length;
if (m[1]) {
decrement = true;
}
base = parseInt(m[2] || 1, 10) - 1;
}
if (decrement && total && typeof value === 'number') {
value = total - value + 1;
}
value += base;
return [str.substring(pos, j), that.zeroPadString(value + '', pad)];
});
},
/**
* Check if string matches against reTag
regexp. This
* function may be used to test if provided string contains HTML tags
* @param {String} str
* @returns {Boolean}
*/
matchesTag: function(str) {
return this.reTag.test(str || '');
},
/**
* Escapes special characters used in Emmet, like '$', '|', etc.
* Use this method before passing to actions like "Wrap with Abbreviation"
* to make sure that existing special characters won't be altered
* @param {String} text
* @return {String}
*/
escapeText: function(text) {
return text.replace(/([\$\\])/g, '\\$1');
},
/**
* Unescapes special characters used in Emmet, like '$', '|', etc.
* @param {String} text
* @return {String}
*/
unescapeText: function(text) {
return text.replace(/\\(.)/g, '$1');
},
/**
* Returns caret placeholder
* @returns {String}
*/
getCaretPlaceholder: function() {
return typeof caretPlaceholder === 'function'
? caretPlaceholder.apply(this, arguments)
: caretPlaceholder;
},
/**
* Sets new representation for carets in generated output
* @param {String} value New caret placeholder. Might be a
* Function
*/
setCaretPlaceholder: function(value) {
caretPlaceholder = value;
},
/**
* Returns line padding
* @param {String} line
* @return {String}
*/
getLinePadding: function(line) {
return (line.match(/^(\s+)/) || [''])[0];
},
/**
* Helper function that returns padding of line of pos
* position in content
* @param {String} content
* @param {Number} pos
* @returns {String}
*/
getLinePaddingFromPosition: function(content, pos) {
var lineRange = this.findNewlineBounds(content, pos);
return this.getLinePadding(lineRange.substring(content));
},
/**
* Escape special regexp chars in string, making it usable for creating dynamic
* regular expressions
* @param {String} str
* @return {String}
*/
escapeForRegexp: function(str) {
var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\
return str.replace(specials, "\\$&");
},
/**
* Make decimal number look good: convert it to fixed precision end remove
* traling zeroes
* @param {Number} num
* @param {Number} fracion Fraction numbers (default is 2)
* @return {String}
*/
prettifyNumber: function(num, fraction) {
return num.toFixed(typeof fraction == 'undefined' ? 2 : fraction).replace(/\.?0+$/, '');
},
/**
* Replace substring of str
with value
* @param {String} str String where to replace substring
* @param {String} value New substring value
* @param {Number} start Start index of substring to replace. May also
* be a Range
object: in this case, the end
* argument is not required
* @param {Number} end End index of substring to replace. If ommited,
* start
argument is used
*/
replaceSubstring: function(str, value, start, end) {
if (typeof start === 'object' && 'end' in start) {
end = start.end;
start = start.start;
}
if (typeof end === 'string') {
end = start + end.length;
}
if (typeof end === 'undefined') {
end = start;
}
if (start < 0 || start > str.length)
return str;
return str.substring(0, start) + value + str.substring(end);
},
/**
* Fills substrings in `content`, defined by given ranges,
* wich `ch` character
* @param {String} content
* @param {Array} ranges
* @return {String}
*/
replaceWith: function(content, ranges, ch, noRepeat) {
if (ranges.length) {
var offset = 0, fragments = [];
ranges.forEach(function(r) {
var repl = noRepeat ? ch : this.repeatString(ch, r[1] - r[0]);
fragments.push(content.substring(offset, r[0]), repl);
offset = r[1];
}, this);
content = fragments.join('') + content.substring(offset);
}
return content;
},
/**
* Narrows down text range, adjusting selection to non-space characters
* @param {String} text
* @param {Number} start Starting range in text
where
* slection should be adjusted. Can also be any object that is accepted
* by Range
class
* @return {Range}
*/
narrowToNonSpace: function(text, start, end) {
var rng = range.create(start, end);
var reSpace = /[\s\n\r\u00a0]/;
// narrow down selection until first non-space character
while (rng.start < rng.end) {
if (!reSpace.test(text.charAt(rng.start)))
break;
rng.start++;
}
while (rng.end > rng.start) {
rng.end--;
if (!reSpace.test(text.charAt(rng.end))) {
rng.end++;
break;
}
}
return rng;
},
/**
* Find start and end index of text line for from
index
* @param {String} text
* @param {Number} from
*/
findNewlineBounds: function(text, from) {
var len = text.length,
start = 0,
end = len - 1,
ch;
// search left
for (var i = from - 1; i > 0; i--) {
ch = text.charAt(i);
if (ch == '\n' || ch == '\r') {
start = i + 1;
break;
}
}
// search right
for (var j = from; j < len; j++) {
ch = text.charAt(j);
if (ch == '\n' || ch == '\r') {
end = j;
break;
}
}
return range.create(start, end - start);
},
/**
* Deep merge of two or more objects. Taken from jQuery.extend()
*/
deepMerge: function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length;
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object' && typeof target !== 'function') {
target = {};
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) !== null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( copy && ( typeof copy === 'object' || (copyIsArray = Array.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else {
clone = src && typeof src === 'object' ? src : {};
}
// Never move original objects, clone them
target[ name ] = this.deepMerge(clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
},
/**
* Dead simple string-to-JSON parser
* @param {String} str
* @returns {Object}
*/
parseJSON: function(str) {
if (typeof str == 'object') {
return str;
}
try {
return JSON.parse(str);
} catch(e) {
return {};
}
},
/**************************
* Utility belt
**************************/
unique: function(arr, comparator) {
var lookup = [];
return arr.filter(function(item) {
var val = comparator ? comparator(item) : item;
if (lookup.indexOf(val) < 0) {
lookup.push(val);
return true;
}
});
},
/**
* Return a copy of the object, filtered to only have values for
* the whitelisted keys.
* @param {Object} obj
* @return {Object}
*/
pick: function(obj) {
var result = {};
var keys = this.toArray(arguments, 1);
Object.keys(obj).forEach(function(key) {
if (~keys.indexOf(key)) {
result[key] = obj[key];
}
});
return result;
},
find: function(arr, comparator, ctx) {
var result;
if (ctx) {
comparator = comparator.bind(ctx);
}
if (Array.isArray(arr)) {
arr.some(function(item, i) {
if (comparator(item, i)) {
return result = item;
}
});
} else {
Object.keys(arr).some(function(key, i) {
if (comparator(arr[key], i)) {
return result = arr[key];
}
});
}
return result;
},
toArray: function(obj, sliceIx) {
if (Array.isArray(obj) && !sliceIx) {
return obj;
}
return Array.prototype.slice.call(obj, sliceIx || 0);
},
extend: function(obj) {
for (var i = 1, il = arguments.length, a; i < il; i++) {
a = arguments[i];
if (a) {
Object.keys(a).forEach(function(key) {
obj[key] = a[key];
});
}
}
return obj;
},
defaults: function(obj) {
for (var i = 1, il = arguments.length, a; i < il; i++) {
a = arguments[i];
if (a) {
Object.keys(a).forEach(function(key) {
if (!(key in obj)) {
obj[key] = a[key];
}
});
}
}
return obj;
},
flatten: function(arr, out) {
out = out || [];
var self = this;
self.toArray(arr).forEach(function(item) {
if (Array.isArray(item)) {
self.flatten(item, out);
} else {
out.push(item);
}
});
return out;
},
clone: function(obj) {
if (Array.isArray(obj)) {
return obj.slice(0);
}
return this.extend({}, obj);
},
without: function(arr) {
this.toArray(arguments, 1).forEach(function(item) {
var ix;
while (~(ix = arr.indexOf(item))) {
arr.splice(ix, 1);
}
});
return arr;
},
last: function(arr) {
return arr[arr.length - 1];
}
};
});
},{"../assets/range":"assets/range.js"}],"utils/cssSections.js":[function(require,module,exports){
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('./common');
var commentsUtils = require('./comments');
var range = require('../assets/range');
var stringStream = require('../assets/stringStream');
var cssParser = require('../parser/css');
var htmlMatcher = require('../assets/htmlMatcher');
var xmlEditTree = require('../editTree/xml');
var idCounter = 1;
var maxId = 1000000;
var reSpaceTrim = /^(\s*).+?(\s*)$/;
var reSpace = /\s/g;
var reComma = /,/;
function isQuote(ch) {
return ch == '"' || ch == "'";
}
function getId() {
idCounter = (idCounter + 1) % maxId;
return 's' + idCounter;
}
/**
* @param {Range} range Full selector range with additional
* properties for matching name and content (@see findAllRules())
* @param {String} source CSS source
*/
function CSSSection(rng, source) {
this.id = getId();
/** @type {CSSSection} */
this.parent = null;
/** @type {CSSSection} */
this.nextSibling = null;
/** @type {CSSSection} */
this.previousSibling = null;
this._source = source;
this._name = null;
this._content = null;
/**
* Custom data for current nodes, used by other modules for
* caching etc.
* @type {Object}
*/
this._data = {};
if (!rng && source) {
rng = range(0, source);
}
this.range = rng;
this.children = [];
}
CSSSection.prototype = {
addChild: function(section) {
if (!(section instanceof CSSSection)) {
section = new CSSSection(section);
}
var lastChild = utils.last(this.children);
if (lastChild) {
lastChild.nextSibling = section;
section.previousSibling = lastChild;
}
section.parent = this;
this.children.push(section);
return section;
},
/**
* Returns root node
* @return {CSSSection}
*/
root: function() {
var root = this;
do {
if (!root.parent) {
return root;
}
} while(root = root.parent);
return root;
},
/**
* Returns currect CSS source
* @return {String}
*/
source: function() {
return this._source || this.root()._source;
},
/**
* Returns section name
* @return {String}
*/
name: function() {
if (this._name === null) {
var range = this.nameRange();
if (range) {
this._name = range.substring(this.source());
}
}
return this._name;
},
/**
* Returns section name range
* @return {[type]} [description]
*/
nameRange: function() {
if (this.range && '_selectorEnd' in this.range) {
return range.create2(this.range.start, this.range._selectorEnd);
}
},
/**
* Returns deepest child of current section (or section itself)
* which includes given position.
* @param {Number} pos
* @return {CSSSection}
*/
matchDeep: function(pos) {
if (!this.range.inside(pos)) {
return null;
}
for (var i = 0, il = this.children.length, m; i < il; i++) {
m = this.children[i].matchDeep(pos);
if (m) {
return m;
}
};
return this.parent ? this : null;
},
/**
* Returns current and all nested sections ranges
* @return {Array}
*/
allRanges: function() {
var out = [];
if (this.parent) {
// add current range if it is not root node
out.push(this.range);
}
this.children.forEach(function(child) {
out = out.concat(child.allRanges());
});
return out;
},
data: function(key, value) {
if (typeof value !== 'undefined') {
this._data[key] = value;
}
return this._data[key];
},
stringify: function(indent) {
indent = indent || '';
var out = '';
this.children.forEach(function(item) {
out += indent + item.name().replace(/\n/g, '\\n') + '\n';
out += item.stringify(indent + '--');
});
return out;
},
/**
* Returns current section’s actual content,
* e.g. content without nested sections
* @return {String}
*/
content: function() {
if (this._content !== null) {
return this._content;
}
if (!this.range || !('_contentStart' in this.range)) {
return '';
}
var r = range.create2(this.range._contentStart + 1, this.range.end - 1);
var source = this.source();
var start = r.start;
var out = '';
this.children.forEach(function(child) {
out += source.substring(start, child.range.start);
start = child.range.end;
});
out += source.substring(start, r.end);
return this._content = utils.trim(out);
}
};
return {
/**
* Finds all CSS rules‘ ranges in given CSS source
* @param {String} content CSS source
* @return {Array} Array of ranges
*/
findAllRules: function(content) {
content = this.sanitize(content);
var stream = stringStream(content);
var ranges = [], matchedRanges;
var self = this;
var saveRule = function(r) {
var selRange = self.extractSelector(content, r.start);
var rule = range.create2(selRange.start, r.end);
rule._selectorEnd = selRange.end;
rule._contentStart = r.start;
ranges.push(rule);
};
var ch;
while (ch = stream.next()) {
if (isQuote(ch)) {
if (!stream.skipString(ch)) {
break; // unterminated string
}
continue;
}
if (ch == '{') {
matchedRanges = this.matchBracesRanges(content, stream.pos - 1);
matchedRanges.forEach(saveRule);
if (matchedRanges.length) {
stream.pos = utils.last(matchedRanges).end;
continue;
}
}
}
return ranges.sort(function(a, b) {
return a.start - b.start;
});
},
/**
* Matches curly braces content right after given position
* @param {String} content CSS content. Must not contain comments!
* @param {Number} pos Search start position
* @return {Range}
*/
matchBracesRanges: function(content, pos, sanitize) {
if (sanitize) {
content = this.sanitize(content);
}
var stream = stringStream(content);
stream.start = stream.pos = pos;
var stack = [], ranges = [];
var ch;
while (ch = stream.next()) {
if (ch == '{') {
stack.push(stream.pos - 1);
} else if (ch == '}') {
if (!stack.length) {
throw 'Invalid source structure (check for curly braces)';
}
ranges.push(range.create2(stack.pop(), stream.pos));
if (!stack.length) {
return ranges;
}
} else {
stream.skipQuoted();
}
}
return ranges;
},
/**
* Extracts CSS selector from CSS document from
* given position. The selector is located by moving backward
* from given position which means that passed position
* must point to the end of selector
* @param {String} content CSS source
* @param {Number} pos Search position
* @param {Boolean} sanitize Sanitize CSS source before processing.
* Off by default and assumes that CSS must be comment-free already
* (for performance)
* @return {Range}
*/
extractSelector: function(content, pos, sanitize) {
if (sanitize) {
content = this.sanitize(content);
}
var skipString = function() {
var quote = content.charAt(pos);
if (quote == '"' || quote == "'") {
while (--pos >= 0) {
if (content.charAt(pos) == quote && content.charAt(pos - 1) != '\\') {
break;
}
}
return true;
}
return false;
};
// find CSS selector
var ch;
var endPos = pos;
while (--pos >= 0) {
if (skipString()) continue;
ch = content.charAt(pos);
if (ch == ')') {
// looks like it’s a preprocessor thing,
// most likely a mixin arguments list, e.g.
// .mixin (@arg1; @arg2) {...}
while (--pos >= 0) {
if (skipString()) continue;
if (content.charAt(pos) == '(') {
break;
}
}
continue;
}
if (ch == '{' || ch == '}' || ch == ';') {
pos++;
break;
}
}
if (pos < 0) {
pos = 0;
}
var selector = content.substring(pos, endPos);
// trim whitespace from matched selector
var m = selector.replace(reSpace, ' ').match(reSpaceTrim);
if (m) {
pos += m[1].length;
endPos -= m[2].length;
}
return range.create2(pos, endPos);
},
/**
* Search for nearest CSS rule/section that contains
* given position
* @param {String} content CSS content or matched CSS rules (array of ranges)
* @param {Number} pos Search position
* @return {Range}
*/
matchEnclosingRule: function(content, pos) {
if (typeof content === 'string') {
content = this.findAllRules(content);
}
var rules = content.filter(function(r) {
return r.inside(pos);
});
return utils.last(rules);
},
/**
* Locates CSS rule next or before given position
* @param {String} content CSS content
* @param {Number} pos Search start position
* @param {Boolean} isBackward Search backward (find previous rule insteaf of next one)
* @return {Range}
*/
locateRule: function(content, pos, isBackward) {
// possible case: editor reported that current syntax is
// CSS, but it’s actually a HTML document (either `style` tag or attribute)
var offset = 0;
var subrange = this.styleTagRange(content, pos);
if (subrange) {
offset = subrange.start;
pos -= subrange.start;
content = subrange.substring(content);
}
var rules = this.findAllRules(content);
var ctxRule = this.matchEnclosingRule(rules, pos);
if (ctxRule) {
return ctxRule.shift(offset);
}
for (var i = 0, il = rules.length; i < il; i++) {
if (rules[i].start > pos) {
return rules[isBackward && i > 0 ? i - 1 : i].shift(offset);
}
}
},
/**
* Sanitizes given CSS content: replaces content that may
* interfere with parsing (comments, interpolations, etc.)
* with spaces. Sanitized content MUST NOT be used for
* editing or outputting, it just simplifies searching
* @param {String} content CSS content
* @return {String}
*/
sanitize: function(content) {
content = commentsUtils.strip(content);
// remove preprocessor string interpolations like #{var}
var stream = stringStream(content);
var replaceRanges = [];
var ch, ch2;
while ((ch = stream.next())) {
if (isQuote(ch)) {
// skip string
stream.skipString(ch)
continue;
} else if (ch === '#' || ch === '@') {
ch2 = stream.peek();
if (ch2 === '{') { // string interpolation
stream.start = stream.pos - 1;
if (stream.skipTo('}')) {
stream.pos += 1;
} else {
throw 'Invalid string interpolation at ' + stream.start;
}
replaceRanges.push([stream.start, stream.pos]);
}
}
}
return utils.replaceWith(content, replaceRanges, 'a');
},
/**
* Parses and returns all sections in given CSS
* as tree-like structure, e.g. provides nesting
* info
* @param {String} content CSS content
* @return {CSSSection}
*/
sectionTree: function(content) {
var root = new CSSSection(null, content);
var rules = this.findAllRules(content);
// rules are sorted in order they appear in CSS source
// so we can optimize their nesting routine
var insert = function(range, ctx) {
while (ctx && ctx.range) {
if (ctx.range.contains(range)) {
return ctx.addChild(range);
}
ctx = ctx.parent;
}
// if we are here then given range is a top-level section
return root.addChild(range);
};
var ctx = root;
rules.forEach(function(r) {
ctx = insert(r, ctx);
});
return root;
},
/**
* Returns ranges for all nested sections, available in
* given CSS rule
* @param {CSSEditContainer} rule
* @return {Array}
*/
nestedSectionsInRule: function(rule) {
var offset = rule.valueRange(true).start;
var nestedSections = this.findAllRules(rule.valueRange().substring(rule.source));
nestedSections.forEach(function(section) {
section.start += offset;
section.end += offset;
section._selectorEnd += offset;
section._contentStart += offset;
});
return nestedSections;
},
styleTagRange: function(content, pos) {
var tag = htmlMatcher.tag(content, pos);
return tag && tag.open.name.toLowerCase() == 'style'
&& tag.innerRange.cmp(pos, 'lte', 'gte')
&& tag.innerRange;
},
styleAttrRange: function(content, pos) {
var tree = xmlEditTree.parseFromPosition(content, pos, true);
if (tree) {
var attr = tree.itemFromPosition(pos, true);
return attr && attr.name().toLowerCase() == 'style'
&& attr.valueRange(true).cmp(pos, 'lte', 'gte')
&& attr.valueRange(true);
}
},
CSSSection: CSSSection
};
});
},{"../assets/htmlMatcher":"assets/htmlMatcher.js","../assets/range":"assets/range.js","../assets/stringStream":"assets/stringStream.js","../editTree/xml":"editTree/xml.js","../parser/css":"parser/css.js","./comments":"utils/comments.js","./common":"utils/common.js"}],"utils/editor.js":[function(require,module,exports){
/**
* Utility module used to prepare text for pasting into back-end editor
* @author Sergey Chikuyonok (serge.che@gmail.com)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('./common');
var resources = require('../assets/resources');
return {
/**
* Check if cursor is placed inside XHTML tag
* @param {String} html Contents of the document
* @param {Number} caretPos Current caret position inside tag
* @return {Boolean}
*/
isInsideTag: function(html, caretPos) {
var reTag = /^<\/?\w[\w\:\-]*.*?>/;
// search left to find opening brace
var pos = caretPos;
while (pos > -1) {
if (html.charAt(pos) == '<')
break;
pos--;
}
if (pos != -1) {
var m = reTag.exec(html.substring(pos));
if (m && caretPos > pos && caretPos < pos + m[0].length)
return true;
}
return false;
},
/**
* Sanitizes incoming editor data and provides default values for
* output-specific info
* @param {IEmmetEditor} editor
* @param {String} syntax
* @param {String} profile
*/
outputInfo: function(editor, syntax, profile) {
// most of this code makes sense for Java/Rhino environment
// because string that comes from Java are not actually JS string
// but Java String object so the have to be explicitly converted
// to native string
profile = profile || editor.getProfileName();
return {
/** @memberOf outputInfo */
syntax: String(syntax || editor.getSyntax()),
profile: profile || null,
content: String(editor.getContent())
};
},
/**
* Unindent content, thus preparing text for tag wrapping
* @param {IEmmetEditor} editor Editor instance
* @param {String} text
* @return {String}
*/
unindent: function(editor, text) {
return utils.unindentString(text, this.getCurrentLinePadding(editor));
},
/**
* Returns padding of current editor's line
* @param {IEmmetEditor} Editor instance
* @return {String}
*/
getCurrentLinePadding: function(editor) {
return utils.getLinePadding(editor.getCurrentLine());
},
/**
* Normalizes content according to given preferences, e.g.
* replaces newlines and indentation with ones defined in
* `options`. If options are not provided or incomplete,
* values will be taken from current user environment
* @param {String} text
* @param {Object} options
* @return {String}
*/
normalize: function(text, options) {
options = utils.extend({
newline: resources.getNewline(),
indentation: resources.getVariable('indentation')
}, options);
var indent = function(tabs) {
return utils.repeatString(options.indentation, tabs.length);
};
var lines = utils.splitByLines(text);
// normailze indentation if it’s not tabs
if (options.indentation !== '\t') {
lines = lines.map(function(line) {
return line.replace(/^\s+/, function(space) {
return space.replace(/\t/g, indent);
});
});
}
// normalize newlines
return lines.join(options.newline);
}
};
});
},{"../assets/resources":"assets/resources.js","./common":"utils/common.js"}],"utils/math.js":[function(require,module,exports){
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
/*
Source: https://github.com/silentmatt/js-expression-eval
Based on ndef.parser, by Raphael Graf(r@undefined.ch)
http://www.undefined.ch/mparser/index.html
Ported to JavaScript and modified by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/)
You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
but don't feel like you have to let me know or ask permission.
*/
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var TNUMBER = 0;
var TOP1 = 1;
var TOP2 = 2;
var TVAR = 3;
var TFUNCALL = 4;
function Token(type_, index_, prio_, number_) {
this.type_ = type_;
this.index_ = index_ || 0;
this.prio_ = prio_ || 0;
this.number_ = (number_ !== undefined && number_ !== null) ? number_ : 0;
this.toString = function () {
switch (this.type_) {
case TNUMBER:
return this.number_;
case TOP1:
case TOP2:
case TVAR:
return this.index_;
case TFUNCALL:
return "CALL";
default:
return "Invalid Token";
}
};
}
function Expression(tokens, ops1, ops2, functions) {
this.tokens = tokens;
this.ops1 = ops1;
this.ops2 = ops2;
this.functions = functions;
}
// Based on http://www.json.org/json2.js
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\'\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
"'" : "\\'",
'\\': '\\\\'
};
function escapeValue(v) {
if (typeof v === "string") {
escapable.lastIndex = 0;
return escapable.test(v) ?
"'" + v.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + "'" :
"'" + v + "'";
}
return v;
}
Expression.prototype = {
simplify: function (values) {
values = values || {};
var nstack = [];
var newexpression = [];
var n1;
var n2;
var f;
var L = this.tokens.length;
var item;
var i = 0;
for (i = 0; i < L; i++) {
item = this.tokens[i];
var type_ = item.type_;
if (type_ === TNUMBER) {
nstack.push(item);
}
else if (type_ === TVAR && (item.index_ in values)) {
item = new Token(TNUMBER, 0, 0, values[item.index_]);
nstack.push(item);
}
else if (type_ === TOP2 && nstack.length > 1) {
n2 = nstack.pop();
n1 = nstack.pop();
f = this.ops2[item.index_];
item = new Token(TNUMBER, 0, 0, f(n1.number_, n2.number_));
nstack.push(item);
}
else if (type_ === TOP1 && nstack.length > 0) {
n1 = nstack.pop();
f = this.ops1[item.index_];
item = new Token(TNUMBER, 0, 0, f(n1.number_));
nstack.push(item);
}
else {
while (nstack.length > 0) {
newexpression.push(nstack.shift());
}
newexpression.push(item);
}
}
while (nstack.length > 0) {
newexpression.push(nstack.shift());
}
return new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions));
},
substitute: function (variable, expr) {
if (!(expr instanceof Expression)) {
expr = new Parser().parse(String(expr));
}
var newexpression = [];
var L = this.tokens.length;
var item;
var i = 0;
for (i = 0; i < L; i++) {
item = this.tokens[i];
var type_ = item.type_;
if (type_ === TVAR && item.index_ === variable) {
for (var j = 0; j < expr.tokens.length; j++) {
var expritem = expr.tokens[j];
var replitem = new Token(expritem.type_, expritem.index_, expritem.prio_, expritem.number_);
newexpression.push(replitem);
}
}
else {
newexpression.push(item);
}
}
var ret = new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions));
return ret;
},
evaluate: function (values) {
values = values || {};
var nstack = [];
var n1;
var n2;
var f;
var L = this.tokens.length;
var item;
var i = 0;
for (i = 0; i < L; i++) {
item = this.tokens[i];
var type_ = item.type_;
if (type_ === TNUMBER) {
nstack.push(item.number_);
}
else if (type_ === TOP2) {
n2 = nstack.pop();
n1 = nstack.pop();
f = this.ops2[item.index_];
nstack.push(f(n1, n2));
}
else if (type_ === TVAR) {
if (item.index_ in values) {
nstack.push(values[item.index_]);
}
else if (item.index_ in this.functions) {
nstack.push(this.functions[item.index_]);
}
else {
throw new Error("undefined variable: " + item.index_);
}
}
else if (type_ === TOP1) {
n1 = nstack.pop();
f = this.ops1[item.index_];
nstack.push(f(n1));
}
else if (type_ === TFUNCALL) {
n1 = nstack.pop();
f = nstack.pop();
if (f.apply && f.call) {
if (Object.prototype.toString.call(n1) == "[object Array]") {
nstack.push(f.apply(undefined, n1));
}
else {
nstack.push(f.call(undefined, n1));
}
}
else {
throw new Error(f + " is not a function");
}
}
else {
throw new Error("invalid Expression");
}
}
if (nstack.length > 1) {
throw new Error("invalid Expression (parity)");
}
return nstack[0];
},
toString: function (toJS) {
var nstack = [];
var n1;
var n2;
var f;
var L = this.tokens.length;
var item;
var i = 0;
for (i = 0; i < L; i++) {
item = this.tokens[i];
var type_ = item.type_;
if (type_ === TNUMBER) {
nstack.push(escapeValue(item.number_));
}
else if (type_ === TOP2) {
n2 = nstack.pop();
n1 = nstack.pop();
f = item.index_;
if (toJS && f == "^") {
nstack.push("Math.pow(" + n1 + "," + n2 + ")");
}
else {
nstack.push("(" + n1 + f + n2 + ")");
}
}
else if (type_ === TVAR) {
nstack.push(item.index_);
}
else if (type_ === TOP1) {
n1 = nstack.pop();
f = item.index_;
if (f === "-") {
nstack.push("(" + f + n1 + ")");
}
else {
nstack.push(f + "(" + n1 + ")");
}
}
else if (type_ === TFUNCALL) {
n1 = nstack.pop();
f = nstack.pop();
nstack.push(f + "(" + n1 + ")");
}
else {
throw new Error("invalid Expression");
}
}
if (nstack.length > 1) {
throw new Error("invalid Expression (parity)");
}
return nstack[0];
},
variables: function () {
var L = this.tokens.length;
var vars = [];
for (var i = 0; i < L; i++) {
var item = this.tokens[i];
if (item.type_ === TVAR && (vars.indexOf(item.index_) == -1)) {
vars.push(item.index_);
}
}
return vars;
},
toJSFunction: function (param, variables) {
var f = new Function(param, "with(Parser.values) { return " + this.simplify(variables).toString(true) + "; }");
return f;
}
};
function add(a, b) {
return Number(a) + Number(b);
}
function sub(a, b) {
return a - b;
}
function mul(a, b) {
return a * b;
}
function div(a, b) {
return a / b;
}
function mod(a, b) {
return a % b;
}
function concat(a, b) {
return "" + a + b;
}
function neg(a) {
return -a;
}
function random(a) {
return Math.random() * (a || 1);
}
function fac(a) { //a!
a = Math.floor(a);
var b = a;
while (a > 1) {
b = b * (--a);
}
return b;
}
// TODO: use hypot that doesn't overflow
function pyt(a, b) {
return Math.sqrt(a * a + b * b);
}
function append(a, b) {
if (Object.prototype.toString.call(a) != "[object Array]") {
return [a, b];
}
a = a.slice();
a.push(b);
return a;
}
function Parser() {
this.success = false;
this.errormsg = "";
this.expression = "";
this.pos = 0;
this.tokennumber = 0;
this.tokenprio = 0;
this.tokenindex = 0;
this.tmpprio = 0;
this.ops1 = {
"sin": Math.sin,
"cos": Math.cos,
"tan": Math.tan,
"asin": Math.asin,
"acos": Math.acos,
"atan": Math.atan,
"sqrt": Math.sqrt,
"log": Math.log,
"abs": Math.abs,
"ceil": Math.ceil,
"floor": Math.floor,
"round": Math.round,
"-": neg,
"exp": Math.exp
};
this.ops2 = {
"+": add,
"-": sub,
"*": mul,
"/": div,
"%": mod,
"^": Math.pow,
",": append,
"||": concat
};
this.functions = {
"random": random,
"fac": fac,
"min": Math.min,
"max": Math.max,
"pyt": pyt,
"pow": Math.pow,
"atan2": Math.atan2
};
this.consts = {
"E": Math.E,
"PI": Math.PI
};
}
Parser.parse = function (expr) {
return new Parser().parse(expr);
};
Parser.evaluate = function (expr, variables) {
return Parser.parse(expr).evaluate(variables);
};
Parser.Expression = Expression;
Parser.values = {
sin: Math.sin,
cos: Math.cos,
tan: Math.tan,
asin: Math.asin,
acos: Math.acos,
atan: Math.atan,
sqrt: Math.sqrt,
log: Math.log,
abs: Math.abs,
ceil: Math.ceil,
floor: Math.floor,
round: Math.round,
random: random,
fac: fac,
exp: Math.exp,
min: Math.min,
max: Math.max,
pyt: pyt,
pow: Math.pow,
atan2: Math.atan2,
E: Math.E,
PI: Math.PI
};
var PRIMARY = 1 << 0;
var OPERATOR = 1 << 1;
var FUNCTION = 1 << 2;
var LPAREN = 1 << 3;
var RPAREN = 1 << 4;
var COMMA = 1 << 5;
var SIGN = 1 << 6;
var CALL = 1 << 7;
var NULLARY_CALL = 1 << 8;
Parser.prototype = {
parse: function (expr) {
this.errormsg = "";
this.success = true;
var operstack = [];
var tokenstack = [];
this.tmpprio = 0;
var expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
var noperators = 0;
this.expression = expr;
this.pos = 0;
while (this.pos < this.expression.length) {
if (this.isOperator()) {
if (this.isSign() && (expected & SIGN)) {
if (this.isNegativeSign()) {
this.tokenprio = 2;
this.tokenindex = "-";
noperators++;
this.addfunc(tokenstack, operstack, TOP1);
}
expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
}
else if (this.isComment()) {
}
else {
if ((expected & OPERATOR) === 0) {
this.error_parsing(this.pos, "unexpected operator");
}
noperators += 2;
this.addfunc(tokenstack, operstack, TOP2);
expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
}
}
else if (this.isNumber()) {
if ((expected & PRIMARY) === 0) {
this.error_parsing(this.pos, "unexpected number");
}
var token = new Token(TNUMBER, 0, 0, this.tokennumber);
tokenstack.push(token);
expected = (OPERATOR | RPAREN | COMMA);
}
else if (this.isString()) {
if ((expected & PRIMARY) === 0) {
this.error_parsing(this.pos, "unexpected string");
}
var token = new Token(TNUMBER, 0, 0, this.tokennumber);
tokenstack.push(token);
expected = (OPERATOR | RPAREN | COMMA);
}
else if (this.isLeftParenth()) {
if ((expected & LPAREN) === 0) {
this.error_parsing(this.pos, "unexpected \"(\"");
}
if (expected & CALL) {
noperators += 2;
this.tokenprio = -2;
this.tokenindex = -1;
this.addfunc(tokenstack, operstack, TFUNCALL);
}
expected = (PRIMARY | LPAREN | FUNCTION | SIGN | NULLARY_CALL);
}
else if (this.isRightParenth()) {
if (expected & NULLARY_CALL) {
var token = new Token(TNUMBER, 0, 0, []);
tokenstack.push(token);
}
else if ((expected & RPAREN) === 0) {
this.error_parsing(this.pos, "unexpected \")\"");
}
expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL);
}
else if (this.isComma()) {
if ((expected & COMMA) === 0) {
this.error_parsing(this.pos, "unexpected \",\"");
}
this.addfunc(tokenstack, operstack, TOP2);
noperators += 2;
expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
}
else if (this.isConst()) {
if ((expected & PRIMARY) === 0) {
this.error_parsing(this.pos, "unexpected constant");
}
var consttoken = new Token(TNUMBER, 0, 0, this.tokennumber);
tokenstack.push(consttoken);
expected = (OPERATOR | RPAREN | COMMA);
}
else if (this.isOp2()) {
if ((expected & FUNCTION) === 0) {
this.error_parsing(this.pos, "unexpected function");
}
this.addfunc(tokenstack, operstack, TOP2);
noperators += 2;
expected = (LPAREN);
}
else if (this.isOp1()) {
if ((expected & FUNCTION) === 0) {
this.error_parsing(this.pos, "unexpected function");
}
this.addfunc(tokenstack, operstack, TOP1);
noperators++;
expected = (LPAREN);
}
else if (this.isVar()) {
if ((expected & PRIMARY) === 0) {
this.error_parsing(this.pos, "unexpected variable");
}
var vartoken = new Token(TVAR, this.tokenindex, 0, 0);
tokenstack.push(vartoken);
expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL);
}
else if (this.isWhite()) {
}
else {
if (this.errormsg === "") {
this.error_parsing(this.pos, "unknown character");
}
else {
this.error_parsing(this.pos, this.errormsg);
}
}
}
if (this.tmpprio < 0 || this.tmpprio >= 10) {
this.error_parsing(this.pos, "unmatched \"()\"");
}
while (operstack.length > 0) {
var tmp = operstack.pop();
tokenstack.push(tmp);
}
if (noperators + 1 !== tokenstack.length) {
//print(noperators + 1);
//print(tokenstack);
this.error_parsing(this.pos, "parity");
}
return new Expression(tokenstack, object(this.ops1), object(this.ops2), object(this.functions));
},
evaluate: function (expr, variables) {
return this.parse(expr).evaluate(variables);
},
error_parsing: function (column, msg) {
this.success = false;
this.errormsg = "parse error [column " + (column) + "]: " + msg;
throw new Error(this.errormsg);
},
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
addfunc: function (tokenstack, operstack, type_) {
var operator = new Token(type_, this.tokenindex, this.tokenprio + this.tmpprio, 0);
while (operstack.length > 0) {
if (operator.prio_ <= operstack[operstack.length - 1].prio_) {
tokenstack.push(operstack.pop());
}
else {
break;
}
}
operstack.push(operator);
},
isNumber: function () {
var r = false;
var str = "";
while (this.pos < this.expression.length) {
var code = this.expression.charCodeAt(this.pos);
if ((code >= 48 && code <= 57) || code === 46) {
str += this.expression.charAt(this.pos);
this.pos++;
this.tokennumber = parseFloat(str);
r = true;
}
else {
break;
}
}
return r;
},
// Ported from the yajjl JSON parser at http://code.google.com/p/yajjl/
unescape: function(v, pos) {
var buffer = [];
var escaping = false;
for (var i = 0; i < v.length; i++) {
var c = v.charAt(i);
if (escaping) {
switch (c) {
case "'":
buffer.push("'");
break;
case '\\':
buffer.push('\\');
break;
case '/':
buffer.push('/');
break;
case 'b':
buffer.push('\b');
break;
case 'f':
buffer.push('\f');
break;
case 'n':
buffer.push('\n');
break;
case 'r':
buffer.push('\r');
break;
case 't':
buffer.push('\t');
break;
case 'u':
// interpret the following 4 characters as the hex of the unicode code point
var codePoint = parseInt(v.substring(i + 1, i + 5), 16);
buffer.push(String.fromCharCode(codePoint));
i += 4;
break;
default:
throw this.error_parsing(pos + i, "Illegal escape sequence: '\\" + c + "'");
}
escaping = false;
} else {
if (c == '\\') {
escaping = true;
} else {
buffer.push(c);
}
}
}
return buffer.join('');
},
isString: function () {
var r = false;
var str = "";
var startpos = this.pos;
if (this.pos < this.expression.length && this.expression.charAt(this.pos) == "'") {
this.pos++;
while (this.pos < this.expression.length) {
var code = this.expression.charAt(this.pos);
if (code != "'" || str.slice(-1) == "\\") {
str += this.expression.charAt(this.pos);
this.pos++;
}
else {
this.pos++;
this.tokennumber = this.unescape(str, startpos);
r = true;
break;
}
}
}
return r;
},
isConst: function () {
var str;
for (var i in this.consts) {
if (true) {
var L = i.length;
str = this.expression.substr(this.pos, L);
if (i === str) {
this.tokennumber = this.consts[i];
this.pos += L;
return true;
}
}
}
return false;
},
isOperator: function () {
var code = this.expression.charCodeAt(this.pos);
if (code === 43) { // +
this.tokenprio = 0;
this.tokenindex = "+";
}
else if (code === 45) { // -
this.tokenprio = 0;
this.tokenindex = "-";
}
else if (code === 124) { // |
if (this.expression.charCodeAt(this.pos + 1) === 124) {
this.pos++;
this.tokenprio = 0;
this.tokenindex = "||";
}
else {
return false;
}
}
else if (code === 42) { // *
this.tokenprio = 1;
this.tokenindex = "*";
}
else if (code === 47) { // /
this.tokenprio = 2;
this.tokenindex = "/";
}
else if (code === 37) { // %
this.tokenprio = 2;
this.tokenindex = "%";
}
else if (code === 94) { // ^
this.tokenprio = 3;
this.tokenindex = "^";
}
else {
return false;
}
this.pos++;
return true;
},
isSign: function () {
var code = this.expression.charCodeAt(this.pos - 1);
if (code === 45 || code === 43) { // -
return true;
}
return false;
},
isPositiveSign: function () {
var code = this.expression.charCodeAt(this.pos - 1);
if (code === 43) { // -
return true;
}
return false;
},
isNegativeSign: function () {
var code = this.expression.charCodeAt(this.pos - 1);
if (code === 45) { // -
return true;
}
return false;
},
isLeftParenth: function () {
var code = this.expression.charCodeAt(this.pos);
if (code === 40) { // (
this.pos++;
this.tmpprio += 10;
return true;
}
return false;
},
isRightParenth: function () {
var code = this.expression.charCodeAt(this.pos);
if (code === 41) { // )
this.pos++;
this.tmpprio -= 10;
return true;
}
return false;
},
isComma: function () {
var code = this.expression.charCodeAt(this.pos);
if (code === 44) { // ,
this.pos++;
this.tokenprio = -1;
this.tokenindex = ",";
return true;
}
return false;
},
isWhite: function () {
var code = this.expression.charCodeAt(this.pos);
if (code === 32 || code === 9 || code === 10 || code === 13) {
this.pos++;
return true;
}
return false;
},
isOp1: function () {
var str = "";
for (var i = this.pos; i < this.expression.length; i++) {
var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase()) {
if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
break;
}
}
str += c;
}
if (str.length > 0 && (str in this.ops1)) {
this.tokenindex = str;
this.tokenprio = 5;
this.pos += str.length;
return true;
}
return false;
},
isOp2: function () {
var str = "";
for (var i = this.pos; i < this.expression.length; i++) {
var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase()) {
if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
break;
}
}
str += c;
}
if (str.length > 0 && (str in this.ops2)) {
this.tokenindex = str;
this.tokenprio = 5;
this.pos += str.length;
return true;
}
return false;
},
isVar: function () {
var str = "";
for (var i = this.pos; i < this.expression.length; i++) {
var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase()) {
if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
break;
}
}
str += c;
}
if (str.length > 0) {
this.tokenindex = str;
this.tokenprio = 4;
this.pos += str.length;
return true;
}
return false;
},
isComment: function () {
var code = this.expression.charCodeAt(this.pos - 1);
if (code === 47 && this.expression.charCodeAt(this.pos) === 42) {
this.pos = this.expression.indexOf("*/", this.pos) + 2;
if (this.pos === 1) {
this.pos = this.expression.length;
}
return true;
}
return false;
}
};
return Parser;
});
},{}],"utils/template.js":[function(require,module,exports){
/**
* A very simple, ERB-style templating. Basically, just as string substitution.
* The reason to not use default Lo-dash’es `_.template()` implementation
* is because it fails to run in CSP-enabled environments (Chrome extension, Atom)
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var stringStream = require('../assets/stringStream');
var utils = require('./common');
function parseArgs(str) {
var args = [];
var stream = stringStream(str);
while (!stream.eol()) {
if (stream.peek() == ',') {
args.push(utils.trim(stream.current()));
stream.next();
stream.start = stream.pos;
}
stream.next();
}
args.push(utils.trim(stream.current()));
return args.filter(function(a) {
return !!a;
});
}
function parseFunctionCall(str) {
var fnName = null, args;
var stream = stringStream(str);
while (!stream.eol()) {
if (stream.peek() == '(') {
fnName = stream.current();
stream.start = stream.pos;
stream.skipToPair('(', ')', true);
args = stream.current();
args = parseArgs(args.substring(1, args.length - 1));
break;
}
stream.next();
}
return fnName && {
name: fnName,
args: args
};
}
function evalArg(arg, context) {
if (/^['"]/.test(arg)) {
// plain string
return arg.replace(/^(['"])(.+?)\1$/, '$2');
}
if (!isNaN(+arg)) {
// a number
return +arg;
}
// otherwise, treat argument as a property name
if (arg) {
var parts = arg.split('.');
var prop = context;
while (parts.length) {
prop = prop[parts.shift()];
}
return prop;
}
}
function process(template, context) {
return template.replace(/<%[=\-](.+?)%>/g, function(str, match) {
match = utils.trim(match);
var fn = parseFunctionCall(match);
if (fn) {
var fnArgs = fn.args.map(function(arg) {
return evalArg(arg, context);
});
return context[fn.name].apply(context, fnArgs);
}
return evalArg(match, context);
});
}
return function(template, context) {
return context ? process(template, context) : function(context) {
return process(template, context);
};
};
});
},{"../assets/stringStream":"assets/stringStream.js","./common":"utils/common.js"}],"vendor/klass.js":[function(require,module,exports){
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
var utils = require('../utils/common');
/**
* Shared empty constructor function to aid in prototype-chain creation.
*/
var ctor = function(){};
/**
* Helper function to correctly set up the prototype chain, for subclasses.
* Similar to `goog.inherits`, but uses a hash of prototype properties and
* class properties to be extended.
* Took it from Backbone.
* @param {Object} parent
* @param {Object} protoProps
* @param {Object} staticProps
* @returns {Object}
*/
function inherits(parent, protoProps, staticProps) {
var child;
// The constructor function for the new subclass is either defined by
// you (the "constructor" property in your `extend` definition), or
// defaulted by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function() {
parent.apply(this, arguments);
};
}
// Inherit class (static) properties from parent.
utils.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps)
utils.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps)
utils.extend(child, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
}
return {
/**
* The self-propagating extend function for classes.
* Took it from Backbone
* @param {Object} protoProps
* @param {Object} classProps
* @returns {Object}
*/
extend: function(protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
// a hack required to WSH inherit `toString` method
if (protoProps.hasOwnProperty('toString'))
child.prototype.toString = protoProps.toString;
return child;
}
};
});
},{"../utils/common":"utils/common.js"}],"vendor/stringScore.js":[function(require,module,exports){
/*!
* string_score.js: String Scoring Algorithm 0.1.10
*
* http://joshaven.com/string_score
* https://github.com/joshaven/string_score
*
* Copyright (C) 2009-2011 Joshaven Potter
* Special thanks to all of the contributors listed here https://github.com/joshaven/string_score
* MIT license: http://www.opensource.org/licenses/mit-license.php
*
* Date: Tue Mar 1 2011
*/
/**
* Scores a string against another string.
* 'Hello World'.score('he'); //=> 0.5931818181818181
* 'Hello World'.score('Hello'); //=> 0.7318181818181818
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}
define(function(require, exports, module) {
return {
score: function(string, abbreviation, fuzziness) {
// If the string is equal to the abbreviation, perfect match.
if (string == abbreviation) {return 1;}
//if it's not a perfect match and is empty return 0
if(abbreviation == "") {return 0;}
var total_character_score = 0,
abbreviation_length = abbreviation.length,
string_length = string.length,
start_of_string_bonus,
abbreviation_score,
fuzzies=1,
final_score;
// Walk through abbreviation and add up scores.
for (var i = 0,
character_score/* = 0*/,
index_in_string/* = 0*/,
c/* = ''*/,
index_c_lowercase/* = 0*/,
index_c_uppercase/* = 0*/,
min_index/* = 0*/;
i < abbreviation_length;
++i) {
// Find the first case-insensitive match of a character.
c = abbreviation.charAt(i);
index_c_lowercase = string.indexOf(c.toLowerCase());
index_c_uppercase = string.indexOf(c.toUpperCase());
min_index = Math.min(index_c_lowercase, index_c_uppercase);
index_in_string = (min_index > -1) ? min_index : Math.max(index_c_lowercase, index_c_uppercase);
if (index_in_string === -1) {
if (fuzziness) {
fuzzies += 1-fuzziness;
continue;
} else {
return 0;
}
} else {
character_score = 0.1;
}
// Set base score for matching 'c'.
// Same case bonus.
if (string[index_in_string] === c) {
character_score += 0.1;
}
// Consecutive letter & start-of-string Bonus
if (index_in_string === 0) {
// Increase the score when matching first character of the remainder of the string
character_score += 0.6;
if (i === 0) {
// If match is the first character of the string
// & the first character of abbreviation, add a
// start-of-string match bonus.
start_of_string_bonus = 1; //true;
}
}
else {
// Acronym Bonus
// Weighing Logic: Typing the first character of an acronym is as if you
// preceded it with two perfect character matches.
if (string.charAt(index_in_string - 1) === ' ') {
character_score += 0.8; // * Math.min(index_in_string, 5); // Cap bonus at 0.4 * 5
}
}
// Left trim the already matched part of the string
// (forces sequential matching).
string = string.substring(index_in_string + 1, string_length);
total_character_score += character_score;
} // end of for loop
// Uncomment to weigh smaller words higher.
// return total_character_score / string_length;
abbreviation_score = total_character_score / abbreviation_length;
//percentage_of_matched_string = abbreviation_length / string_length;
//word_score = abbreviation_score * percentage_of_matched_string;
// Reduce penalty for longer strings.
//final_score = (word_score + abbreviation_score) / 2;
final_score = ((abbreviation_score * (abbreviation_length / string_length)) + abbreviation_score) / 2;
final_score = final_score / fuzzies;
if (start_of_string_bonus && (final_score + 0.15 < 1)) {
final_score += 0.15;
}
return final_score;
}
};
});
},{}]},{},["./lib/emmet.js"])("./lib/emmet.js")
});
(function() {
var __slice = [].slice;
(function($, emmet) {
var jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, removeEmmetGlobal, root, _, _ref;
root = (1, eval)('this');
originalJasmineFixture = root.jasmineFixture;
originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
originalAffix = root.affix;
removeEmmetGlobal = (function() {
delete root.emmet;
if (root.hasOwnProperty('__jasmineFixtureEmmetNoConflict')) {
root.emmet = root.__jasmineFixtureEmmetNoConflict;
return delete root.__jasmineFixtureEmmetNoConflict;
}
})();
_ = function(list) {
return {
inject: function(iterator, memo) {
var item, _i, _len, _results;
_results = [];
for (_i = 0, _len = list.length; _i < _len; _i++) {
item = list[_i];
_results.push(memo = iterator(memo, item));
}
return _results;
}
};
};
root.jasmineFixture = function($) {
var $whatsTheRootOf, affix, create, ewwSideEffects, jasmineFixture, noConflict;
affix = function(selectorOptions) {
return create.call(this, selectorOptions, true);
};
create = function(selectorOptions, attach) {
var $top;
$top = null;
_(selectorOptions.trim().split(/[ ](?![^\{]*\})(?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
var $el;
if (elementSelector === ">") {
return $parent;
}
$el = $(emmet.parser.expand(elementSelector));
if (attach || $top) {
$el.appendTo($parent);
}
$top || ($top = $el);
return $el;
}, $whatsTheRootOf(this));
return $top;
};
noConflict = function() {
var currentJasmineFixture, _ref1;
currentJasmineFixture = jasmine.fixture;
root.jasmineFixture = originalJasmineFixture;
if ((_ref1 = root.jasmine) != null) {
_ref1.fixture = originalJasmineDotFixture;
}
root.affix = originalAffix;
return currentJasmineFixture;
};
$whatsTheRootOf = function(that) {
if ((that != null ? that.jquery : void 0) != null) {
return that;
} else if ($('#jasmine_content').length > 0) {
return $('#jasmine_content');
} else {
return $('').appendTo('body');
}
};
ewwSideEffects = function(jasmineFixture) {
var _ref1;
if ((_ref1 = root.jasmine) != null) {
_ref1.fixture = jasmineFixture;
}
$.fn.affix = root.affix = jasmineFixture.affix;
return afterEach(function() {
return $('#jasmine_content').remove();
});
};
jasmineFixture = {
affix: affix,
create: create,
noConflict: noConflict
};
ewwSideEffects(jasmineFixture);
return jasmineFixture;
};
if ($) {
return jasmineFixture = root.jasmineFixture($);
} else {
return root.affix = function() {
var nowJQueryExists;
nowJQueryExists = window.jQuery || window.$;
if (nowJQueryExists != null) {
jasmineFixture = root.jasmineFixture(nowJQueryExists);
return affix.call.apply(affix, [this].concat(__slice.call(arguments)));
} else {
throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$");
}
};
}
})(window.jQuery || window.$, emmet);
}).call(this);