undefined
when no more characters are available.
* @returns {Number}
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
* @param {Number|Function} match
* @returns {Boolean}
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls eat
with the given argument, until it
* fails. Returns true
if any characters were eaten.
* @param {Object} match
* @returns {Boolean}
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) {}
return this.pos !== start;
}
/**
* 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(n) {
this.pos -= n || 1;
}
/**
* Get the string between the start of the current token and the
* current stream position.
* @returns {String}
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
* @param {Number} start
* @param {Number} [end]
* @return {String}
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
* @param {String} message
* @return {Error}
*/
error(message) {
const err = new Error(`${message} at char ${this.pos + 1}`);
err.originalMessage = message;
err.pos = this.pos;
err.string = this.string;
return err;
}
};
/* harmony default export */ var stream_reader_es = (StreamReader);
// CONCATENATED MODULE: ../node_modules/@emmetio/stream-reader-utils/dist/stream-reader-utils.es.js
var stream_reader_utils_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* Methods for consuming quoted values
*/
const SINGLE_QUOTE = 39; // '
const DOUBLE_QUOTE = 34; // "
const defaultOptions = {
escape: 92, // \ character
throws: false
};
/**
* Consumes 'single' or "double"-quoted string from given string, if possible
* @param {StreamReader} stream
* @param {Number} options.escape A character code of quote-escape symbol
* @param {Boolean} options.throws Throw error if quotes string can’t be properly consumed
* @return {Boolean} `true` if quoted string was consumed. The contents
* of quoted string will be availabe as `stream.current()`
*/
var eatQuoted = function (stream, options) {
options = options ? stream_reader_utils_es__extends({}, defaultOptions, options) : defaultOptions;
const start = stream.pos;
const quote = stream.peek();
if (stream.eat(isQuote)) {
while (!stream.eof()) {
switch (stream.next()) {
case quote:
stream.start = start;
return true;
case options.escape:
stream.next();
break;
}
}
// If we’re here then stream wasn’t properly consumed.
// Revert stream and decide what to do
stream.pos = start;
if (options.throws) {
throw stream.error('Unable to consume quoted string');
}
}
return false;
};
function isQuote(code) {
return code === SINGLE_QUOTE || code === DOUBLE_QUOTE;
}
/**
* Check if given code is a number
* @param {Number} code
* @return {Boolean}
*/
function isNumber(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
* @param {Number} code
* @param {Number} [from]
* @param {Number} [to]
* @return {Boolean}
*/
function isAlpha(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
* @param {Number} code
* @return {Boolean}
*/
function isAlphaNumeric(code) {
return isNumber(code) || isAlpha(code);
}
function isWhiteSpace(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space
* @param {Number} code
* @return {Boolean}
*/
function isSpace(code) {
return isWhiteSpace(code) || code === 10 /* LF */
|| code === 13; /* CR */
}
const defaultOptions$1 = {
escape: 92, // \ character
throws: false
};
/**
* Eats paired characters substring, for example `(foo)` or `[bar]`
* @param {StreamReader} stream
* @param {Number} open Character code of pair openinig
* @param {Number} close Character code of pair closing
* @param {Object} [options]
* @return {Boolean} Returns `true` if chacarter pair was successfully
* consumed, it’s content will be available as `stream.current()`
*/
function eatPair(stream, open, close, options) {
options = options ? stream_reader_utils_es__extends({}, defaultOptions$1, options) : defaultOptions$1;
const start = stream.pos;
if (stream.eat(open)) {
let stack = 1,
ch;
while (!stream.eof()) {
if (eatQuoted(stream, options)) {
continue;
}
ch = stream.next();
if (ch === open) {
stack++;
} else if (ch === close) {
stack--;
if (!stack) {
stream.start = start;
return true;
}
} else if (ch === options.escape) {
stream.next();
}
}
// If we’re here then paired character can’t be consumed
stream.pos = start;
if (options.throws) {
throw stream.error(`Unable to find matching pair for ${String.fromCharCode(open)}`);
}
}
return false;
}
// CONCATENATED MODULE: ../node_modules/@emmetio/field-parser/dist/field-parser.es.js
const DOLLAR = 36; // $
const COLON = 58; // :
const ESCAPE = 92; // \
const OPEN_BRACE = 123; // {
const CLOSE_BRACE = 125; // }
/**
* Finds fields in given string and returns object with field-less string
* and array of fields found
* @param {String} string
* @return {Object}
*/
function parse(string) {
const stream = new stream_reader_es(string);
const fields = [];
let cleanString = '',
offset = 0,
pos = 0;
let code, field;
while (!stream.eof()) {
code = stream.peek();
pos = stream.pos;
if (code === ESCAPE) {
stream.next();
stream.next();
} else if (field = consumeField(stream, cleanString.length + pos - offset)) {
fields.push(field);
cleanString += stream.string.slice(offset, pos) + field.placeholder;
offset = stream.pos;
} else {
stream.next();
}
}
return new FieldString(cleanString + stream.string.slice(offset), fields);
}
/**
* Marks given `string` with `fields`: wraps each field range with
* `${index:placeholder}` (by default) or any other token produced by `token`
* function, if provided
* @param {String} string String to mark
* @param {Array} fields Array of field descriptor. A field descriptor is a
* `{index, location, length}` array. It is important that fields in array
* must be ordered by their location in string: some fields my refer the same
* location so they must appear in order that user expects.
* @param {Function} [token] Function that generates field token. This function
* received two arguments: `index` and `placeholder` and should return string
* @return {String} String with marked fields
*/
function mark(string, fields, token) {
token = token || createToken;
// order fields by their location and appearence
// NB field ranges should not overlap! (not supported yet)
const ordered = fields.map((field, order) => ({ order, field, end: field.location + field.length })).sort((a, b) => a.end - b.end || a.order - b.order);
// mark ranges in string
let offset = 0;
const result = ordered.map(item => {
const placeholder = string.substr(item.field.location, item.field.length);
const prefix = string.slice(offset, item.field.location);
offset = item.end;
return prefix + token(item.field.index, placeholder);
});
return result.join('') + string.slice(offset);
}
/**
* Creates field token for string
* @param {Number} index Field index
* @param {String} placeholder Field placeholder, could be empty string
* @return {String}
*/
function createToken(index, placeholder) {
return placeholder ? `\${${index}:${placeholder}}` : `\${${index}}`;
}
/**
* Consumes field from current stream position: it can be an `$index` or
* or `${index}` or `${index:placeholder}`
* @param {StreamReader} stream
* @param {Number} location Field location in *clean* string
* @return {Field} Object with `index` and `placeholder` properties if
* field was successfully consumed, `null` otherwise
*/
function consumeField(stream, location) {
const start = stream.pos;
if (stream.eat(DOLLAR)) {
// Possible start of field
let index = consumeIndex(stream);
let placeholder = '';
// consumed $index placeholder
if (index != null) {
return new Field(index, placeholder, location);
}
if (stream.eat(OPEN_BRACE)) {
index = consumeIndex(stream);
if (index != null) {
if (stream.eat(COLON)) {
placeholder = consumePlaceholder(stream);
}
if (stream.eat(CLOSE_BRACE)) {
return new Field(index, placeholder, location);
}
}
}
}
// If we reached here then there’s no valid field here, revert
// back to starting position
stream.pos = start;
}
/**
* Consumes a placeholder: value right after `:` in field. Could be empty
* @param {StreamReader} stream
* @return {String}
*/
function consumePlaceholder(stream) {
let code;
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
code = stream.peek();
if (code === OPEN_BRACE) {
stack.push(stream.pos);
} else if (code === CLOSE_BRACE) {
if (!stack.length) {
break;
}
stack.pop();
}
stream.next();
}
if (stack.length) {
throw stream.error('Unable to find matching "}" for curly brace at ' + stack.pop());
}
return stream.current();
}
/**
* Consumes integer from current stream position
* @param {StreamReader} stream
* @return {Number}
*/
function consumeIndex(stream) {
stream.start = stream.pos;
if (stream.eatWhile(isNumber)) {
return Number(stream.current());
}
}
let Field = class Field {
constructor(index, placeholder, location) {
this.index = index;
this.placeholder = placeholder;
this.location = location;
this.length = this.placeholder.length;
}
};
let FieldString = class FieldString {
/**
* @param {String} string
* @param {Field[]} fields
*/
constructor(string, fields) {
this.string = string;
this.fields = fields;
}
mark(token) {
return mark(this.string, this.fields, token);
}
toString() {
return this.string;
}
};
/* harmony default export */ var field_parser_es = (parse);
//# sourceMappingURL=field-parser.es.js.map
// CONCATENATED MODULE: ../node_modules/@emmetio/extract-abbreviation/dist/extract-abbreviation.es.js
var extract_abbreviation_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* Minimalistic backwards stream reader
*/
let extract_abbreviation_es_StreamReader = class StreamReader {
constructor(string, start) {
this.string = string;
this.start = start || 0;
this.pos = this.string.length;
}
sol() {
return this.pos === this.start;
}
peek(offset) {
return this.string.charCodeAt(this.pos - 1 + (offset || 0));
}
prev() {
if (!this.sol()) {
return this.string.charCodeAt(--this.pos);
}
}
eat(match) {
if (this.sol()) {
return false;
}
const ok = typeof match === 'function' ? match(this.peek()) : match === this.peek();
if (ok) {
this.pos--;
}
return ok;
}
eatWhile(match) {
const start = this.pos;
while (this.eat(match)) {}
return this.pos < start;
}
};
/**
* Quotes-related utilities
*/
const extract_abbreviation_es_SINGLE_QUOTE = 39; // '
const extract_abbreviation_es_DOUBLE_QUOTE = 34; // "
const extract_abbreviation_es_ESCAPE = 92; // \
/**
* Check if given character code is a quote
* @param {Number} c
* @return {Boolean}
*/
function extract_abbreviation_es_isQuote(c) {
return c === extract_abbreviation_es_SINGLE_QUOTE || c === extract_abbreviation_es_DOUBLE_QUOTE;
}
/**
* Consumes quoted value, if possible
* @param {StreamReader} stream
* @return {Boolean} Returns `true` is value was consumed
*/
function extract_abbreviation_es_eatQuoted(stream) {
const start = stream.pos;
const quote = stream.prev();
if (extract_abbreviation_es_isQuote(quote)) {
while (!stream.sol()) {
if (stream.prev() === quote && stream.peek() !== extract_abbreviation_es_ESCAPE) {
return true;
}
}
}
stream.pos = start;
return false;
}
const TAB = 9;
const SPACE = 32;
const DASH = 45; // -
const SLASH = 47; // /
const extract_abbreviation_es_COLON = 58; // :
const EQUALS = 61; // =
const ANGLE_LEFT = 60; // <
const ANGLE_RIGHT = 62; // >
/**
* Check if given reader’s current position points at the end of HTML tag
* @param {StreamReader} stream
* @return {Boolean}
*/
var isAtHTMLTag = function (stream) {
const start = stream.pos;
if (!stream.eat(ANGLE_RIGHT)) {
return false;
}
let ok = false;
stream.eat(SLASH); // possibly self-closed element
while (!stream.sol()) {
stream.eatWhile(extract_abbreviation_es_isWhiteSpace);
if (eatIdent(stream)) {
// ate identifier: could be a tag name, boolean attribute or unquoted
// attribute value
if (stream.eat(SLASH)) {
// either closing tag or invalid tag
ok = stream.eat(ANGLE_LEFT);
break;
} else if (stream.eat(ANGLE_LEFT)) {
// opening tag
ok = true;
break;
} else if (stream.eat(extract_abbreviation_es_isWhiteSpace)) {
// boolean attribute
continue;
} else if (stream.eat(EQUALS)) {
// simple unquoted value or invalid attribute
if (eatIdent(stream)) {
continue;
}
break;
} else if (eatAttributeWithUnquotedValue(stream)) {
// identifier was a part of unquoted value
ok = true;
break;
}
// invalid tag
break;
}
if (eatAttribute(stream)) {
continue;
}
break;
}
stream.pos = start;
return ok;
};
/**
* Eats HTML attribute from given string.
* @param {StreamReader} state
* @return {Boolean} `true` if attribute was consumed.
*/
function eatAttribute(stream) {
return eatAttributeWithQuotedValue(stream) || eatAttributeWithUnquotedValue(stream);
}
/**
* @param {StreamReader} stream
* @return {Boolean}
*/
function eatAttributeWithQuotedValue(stream) {
const start = stream.pos;
if (extract_abbreviation_es_eatQuoted(stream) && stream.eat(EQUALS) && eatIdent(stream)) {
return true;
}
stream.pos = start;
return false;
}
/**
* @param {StreamReader} stream
* @return {Boolean}
*/
function eatAttributeWithUnquotedValue(stream) {
const start = stream.pos;
if (stream.eatWhile(isUnquotedValue) && stream.eat(EQUALS) && eatIdent(stream)) {
return true;
}
stream.pos = start;
return false;
}
/**
* Eats HTML identifier from stream
* @param {StreamReader} stream
* @return {Boolean}
*/
function eatIdent(stream) {
return stream.eatWhile(isIdent);
}
/**
* Check if given character code belongs to HTML identifier
* @param {Number} c
* @return {Boolean}
*/
function isIdent(c) {
return c === extract_abbreviation_es_COLON || c === DASH || extract_abbreviation_es_isAlpha(c) || extract_abbreviation_es_isNumber(c);
}
/**
* Check if given character code is alpha code (letter though A to Z)
* @param {Number} c
* @return {Boolean}
*/
function extract_abbreviation_es_isAlpha(c) {
c &= ~32; // quick hack to convert any char code to uppercase char code
return c >= 65 && c <= 90; // A-Z
}
/**
* Check if given code is a number
* @param {Number} c
* @return {Boolean}
*/
function extract_abbreviation_es_isNumber(c) {
return c > 47 && c < 58;
}
/**
* Check if given code is a whitespace
* @param {Number} c
* @return {Boolean}
*/
function extract_abbreviation_es_isWhiteSpace(c) {
return c === SPACE || c === TAB;
}
/**
* Check if given code may belong to unquoted attribute value
* @param {Number} c
* @return {Boolean}
*/
function isUnquotedValue(c) {
return c && c !== EQUALS && !extract_abbreviation_es_isWhiteSpace(c) && !extract_abbreviation_es_isQuote(c);
}
const extract_abbreviation_es_code = ch => ch.charCodeAt(0);
const SQUARE_BRACE_L = extract_abbreviation_es_code('[');
const SQUARE_BRACE_R = extract_abbreviation_es_code(']');
const ROUND_BRACE_L = extract_abbreviation_es_code('(');
const ROUND_BRACE_R = extract_abbreviation_es_code(')');
const CURLY_BRACE_L = extract_abbreviation_es_code('{');
const CURLY_BRACE_R = extract_abbreviation_es_code('}');
const specialChars = new Set('#.*:$-_!@%^+>/'.split('').map(extract_abbreviation_es_code));
const bracePairs = new Map().set(SQUARE_BRACE_L, SQUARE_BRACE_R).set(ROUND_BRACE_L, ROUND_BRACE_R).set(CURLY_BRACE_L, CURLY_BRACE_R);
const extract_abbreviation_es_defaultOptions = {
syntax: 'markup',
lookAhead: null,
prefix: ''
};
/**
* Extracts Emmet abbreviation from given string.
* The goal of this module is to extract abbreviation from current editor’s line,
* e.g. like this: `.foo[title=bar|]` -> `.foo[title=bar]`, where
* `|` is a current caret position.
* @param {String} line A text line where abbreviation should be expanded
* @param {Number} [pos] Caret position in line. If not given, uses end-of-line
* @param {Object} [options]
* @param {Boolean} [options.lookAhead] Allow parser to look ahead of `pos` index for
* searching of missing abbreviation parts. Most editors automatically inserts
* closing braces for `[`, `{` and `(`, which will most likely be right after
* current caret position. So in order to properly expand abbreviation, user
* must explicitly move caret right after auto-inserted braces. With this option
* enabled, parser will search for closing braces right after `pos`. Default is `true`
* @param {String} [options.syntax] Name of context syntax of expanded abbreviation.
* Either 'markup' (default) or 'stylesheet'. In 'stylesheet' syntax, braces `[]`
* and `{}` are not supported thus not extracted.
* @param {String} [options.prefix] A string that should precede abbreviation in
* order to make it successfully extracted. If given, the abbreviation will be
* extracted from the nearest `prefix` occurrence.
* @return {Object} Object with `abbreviation` and its `location` in given line
* if abbreviation can be extracted, `null` otherwise
*/
function extractAbbreviation(line, pos, options) {
// make sure `pos` is within line range
pos = Math.min(line.length, Math.max(0, pos == null ? line.length : pos));
if (typeof options === 'boolean') {
options = extract_abbreviation_es__extends({}, extract_abbreviation_es_defaultOptions, { lookAhead: options });
} else {
options = extract_abbreviation_es__extends({}, extract_abbreviation_es_defaultOptions, options);
}
if (options.lookAhead == null || options.lookAhead === true) {
pos = offsetPastAutoClosed(line, pos, options);
}
let c;
const start = getStartOffset(line, pos, options.prefix);
if (start === -1) {
return null;
}
const stream = new extract_abbreviation_es_StreamReader(line, start);
stream.pos = pos;
const stack = [];
while (!stream.sol()) {
c = stream.peek();
if (isCloseBrace(c, options.syntax)) {
stack.push(c);
} else if (isOpenBrace(c, options.syntax)) {
if (stack.pop() !== bracePairs.get(c)) {
// unexpected brace
break;
}
} else if (has(stack, SQUARE_BRACE_R) || has(stack, CURLY_BRACE_R)) {
// respect all characters inside attribute sets or text nodes
stream.pos--;
continue;
} else if (isAtHTMLTag(stream) || !isAbbreviation(c)) {
break;
}
stream.pos--;
}
if (!stack.length && stream.pos !== pos) {
// found something, remove some invalid symbols from the
// beginning and return abbreviation
const abbreviation = line.slice(stream.pos, pos).replace(/^[*+>^]+/, '');
return {
abbreviation,
location: pos - abbreviation.length,
start: options.prefix ? start - options.prefix.length : pos - abbreviation.length,
end: pos
};
}
}
/**
* Returns new `line` index which is right after characters beyound `pos` that
* editor will likely automatically close, e.g. }, ], and quotes
* @param {String} line
* @param {Number} pos
* @return {Number}
*/
function offsetPastAutoClosed(line, pos, options) {
// closing quote is allowed only as a next character
if (extract_abbreviation_es_isQuote(line.charCodeAt(pos))) {
pos++;
}
// offset pointer until non-autoclosed character is found
while (isCloseBrace(line.charCodeAt(pos), options.syntax)) {
pos++;
}
return pos;
}
/**
* Returns start offset (left limit) in `line` where we should stop looking for
* abbreviation: it’s nearest to `pos` location of `prefix` token
* @param {String} line
* @param {Number} pos
* @param {String} prefix
* @return {Number}
*/
function getStartOffset(line, pos, prefix) {
if (!prefix) {
return 0;
}
const stream = new extract_abbreviation_es_StreamReader(line);
const compiledPrefix = String(prefix).split('').map(extract_abbreviation_es_code);
stream.pos = pos;
let result;
while (!stream.sol()) {
if (consumePair(stream, SQUARE_BRACE_R, SQUARE_BRACE_L) || consumePair(stream, CURLY_BRACE_R, CURLY_BRACE_L)) {
continue;
}
result = stream.pos;
if (consumeArray(stream, compiledPrefix)) {
return result;
}
stream.pos--;
}
return -1;
}
/**
* Consumes full character pair, if possible
* @param {StreamReader} stream
* @param {Number} close
* @param {Number} open
* @return {Boolean}
*/
function consumePair(stream, close, open) {
const start = stream.pos;
if (stream.eat(close)) {
while (!stream.sol()) {
if (stream.eat(open)) {
return true;
}
stream.pos--;
}
}
stream.pos = start;
return false;
}
/**
* Consumes all character codes from given array, right-to-left, if possible
* @param {StreamReader} stream
* @param {Number[]} arr
*/
function consumeArray(stream, arr) {
const start = stream.pos;
let consumed = false;
for (let i = arr.length - 1; i >= 0 && !stream.sol(); i--) {
if (!stream.eat(arr[i])) {
break;
}
consumed = i === 0;
}
if (!consumed) {
stream.pos = start;
}
return consumed;
}
function has(arr, value) {
return arr.indexOf(value) !== -1;
}
function isAbbreviation(c) {
return c > 64 && c < 91 || // uppercase letter
c > 96 && c < 123 // lowercase letter
|| c > 47 && c < 58 // number
|| specialChars.has(c); // special character
}
function isOpenBrace(c, syntax) {
return c === ROUND_BRACE_L || syntax === 'markup' && (c === SQUARE_BRACE_L || c === CURLY_BRACE_L);
}
function isCloseBrace(c, syntax) {
return c === ROUND_BRACE_R || syntax === 'markup' && (c === SQUARE_BRACE_R || c === CURLY_BRACE_R);
}
/* harmony default export */ var extract_abbreviation_es = (extractAbbreviation);
// CONCATENATED MODULE: ../node_modules/@emmetio/node/dist/node.es.js
var node_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* Attribute descriptor of parsed abbreviation node
* @param {String} name Attribute name
* @param {String} value Attribute value
* @param {Object} options Additional custom attribute options
* @param {Boolean} options.boolean Attribute is boolean (e.g. name equals value)
* @param {Boolean} options.implied Attribute is implied (e.g. must be outputted
* only if contains non-null value)
*/
let Attribute = class Attribute {
constructor(name, value, options) {
this.name = name;
this.value = value != null ? value : null;
this.options = options || {};
}
/**
* Create a copy of current attribute
* @return {Attribute}
*/
clone() {
return new Attribute(this.name, this.value, node_es__extends({}, this.options));
}
/**
* A string representation of current node
*/
valueOf() {
return `${this.name}="${this.value}"`;
}
};
/**
* A parsed abbreviation AST node. Nodes build up an abbreviation AST tree
*/
let Node = class Node {
/**
* Creates a new node
* @param {String} [name] Node name
* @param {Array} [attributes] Array of attributes to add
*/
constructor(name, attributes) {
// own properties
this.name = name || null;
this.value = null;
this.repeat = null;
this.selfClosing = false;
this.children = [];
/** @type {Node} Pointer to parent node */
this.parent = null;
/** @type {Node} Pointer to next sibling */
this.next = null;
/** @type {Node} Pointer to previous sibling */
this.previous = null;
this._attributes = [];
if (Array.isArray(attributes)) {
attributes.forEach(attr => this.setAttribute(attr));
}
}
/**
* Array of current node attributes
* @return {Attribute[]} Array of attributes
*/
get attributes() {
return this._attributes;
}
/**
* A shorthand to retreive node attributes as map
* @return {Object}
*/
get attributesMap() {
return this.attributes.reduce((out, attr) => {
out[attr.name] = attr.options.boolean ? attr.name : attr.value;
return out;
}, {});
}
/**
* Check if current node is a grouping one, e.g. has no actual representation
* and is used for grouping subsequent nodes only
* @return {Boolean}
*/
get isGroup() {
return !this.name && !this.value && !this._attributes.length;
}
/**
* Check if given node is a text-only node, e.g. contains only value
* @return {Boolean}
*/
get isTextOnly() {
return !this.name && !!this.value && !this._attributes.length;
}
/**
* Returns first child node
* @return {Node}
*/
get firstChild() {
return this.children[0];
}
/**
* Returns last child of current node
* @return {Node}
*/
get lastChild() {
return this.children[this.children.length - 1];
}
/**
* Return index of current node in its parent child list
* @return {Number} Returns -1 if current node is a root one
*/
get childIndex() {
return this.parent ? this.parent.children.indexOf(this) : -1;
}
/**
* Returns next sibling of current node
* @return {Node}
*/
get nextSibling() {
return this.next;
}
/**
* Returns previous sibling of current node
* @return {Node}
*/
get previousSibling() {
return this.previous;
}
/**
* Returns array of unique class names in current node
* @return {String[]}
*/
get classList() {
const attr = this.getAttribute('class');
return attr && attr.value ? attr.value.split(/\s+/g).filter(uniqueClass) : [];
}
/**
* Convenient alias to create a new node instance
* @param {String} [name] Node name
* @param {Object} [attributes] Attributes hash
* @return {Node}
*/
create(name, attributes) {
return new Node(name, attributes);
}
/**
* Sets given attribute for current node
* @param {String|Object|Attribute} name Attribute name or attribute object
* @param {String} [value] Attribute value
*/
setAttribute(name, value) {
const attr = createAttribute(name, value);
const curAttr = this.getAttribute(name);
if (curAttr) {
this.replaceAttribute(curAttr, attr);
} else {
this._attributes.push(attr);
}
}
/**
* Check if attribute with given name exists in node
* @param {String} name
* @return {Boolean}
*/
hasAttribute(name) {
return !!this.getAttribute(name);
}
/**
* Returns attribute object by given name
* @param {String} name
* @return {Attribute}
*/
getAttribute(name) {
if (typeof name === 'object') {
name = name.name;
}
for (var i = 0; i < this._attributes.length; i++) {
const attr = this._attributes[i];
if (attr.name === name) {
return attr;
}
}
}
/**
* Replaces attribute with new instance
* @param {String|Attribute} curAttribute Current attribute name or instance
* to replace
* @param {String|Object|Attribute} newName New attribute name or attribute object
* @param {String} [newValue] New attribute value
*/
replaceAttribute(curAttribute, newName, newValue) {
if (typeof curAttribute === 'string') {
curAttribute = this.getAttribute(curAttribute);
}
const ix = this._attributes.indexOf(curAttribute);
if (ix !== -1) {
this._attributes.splice(ix, 1, createAttribute(newName, newValue));
}
}
/**
* Removes attribute with given name
* @param {String|Attribute} attr Atrtibute name or instance
*/
removeAttribute(attr) {
if (typeof attr === 'string') {
attr = this.getAttribute(attr);
}
const ix = this._attributes.indexOf(attr);
if (ix !== -1) {
this._attributes.splice(ix, 1);
}
}
/**
* Removes all attributes from current node
*/
clearAttributes() {
this._attributes.length = 0;
}
/**
* Adds given class name to class attribute
* @param {String} token Class name token
*/
addClass(token) {
token = normalize(token);
if (!this.hasAttribute('class')) {
this.setAttribute('class', token);
} else if (token && !this.hasClass(token)) {
this.setAttribute('class', this.classList.concat(token).join(' '));
}
}
/**
* Check if current node contains given class name
* @param {String} token Class name token
* @return {Boolean}
*/
hasClass(token) {
return this.classList.indexOf(normalize(token)) !== -1;
}
/**
* Removes given class name from class attribute
* @param {String} token Class name token
*/
removeClass(token) {
token = normalize(token);
if (this.hasClass(token)) {
this.setAttribute('class', this.classList.filter(name => name !== token).join(' '));
}
}
/**
* Appends child to current node
* @param {Node} node
*/
appendChild(node) {
this.insertAt(node, this.children.length);
}
/**
* Inserts given `newNode` before `refNode` child node
* @param {Node} newNode
* @param {Node} refNode
*/
insertBefore(newNode, refNode) {
this.insertAt(newNode, this.children.indexOf(refNode));
}
/**
* Insert given `node` at `pos` position of child list
* @param {Node} node
* @param {Number} pos
*/
insertAt(node, pos) {
if (pos < 0 || pos > this.children.length) {
throw new Error('Unable to insert node: position is out of child list range');
}
const prev = this.children[pos - 1];
const next = this.children[pos];
node.remove();
node.parent = this;
this.children.splice(pos, 0, node);
if (prev) {
node.previous = prev;
prev.next = node;
}
if (next) {
node.next = next;
next.previous = node;
}
}
/**
* Removes given child from current node
* @param {Node} node
*/
removeChild(node) {
const ix = this.children.indexOf(node);
if (ix !== -1) {
this.children.splice(ix, 1);
if (node.previous) {
node.previous.next = node.next;
}
if (node.next) {
node.next.previous = node.previous;
}
node.parent = node.next = node.previous = null;
}
}
/**
* Removes current node from its parent
*/
remove() {
if (this.parent) {
this.parent.removeChild(this);
}
}
/**
* Creates a detached copy of current node
* @param {Boolean} deep Clone node contents as well
* @return {Node}
*/
clone(deep) {
const clone = new Node(this.name);
clone.value = this.value;
clone.selfClosing = this.selfClosing;
if (this.repeat) {
clone.repeat = node_es__extends({}, this.repeat);
}
this._attributes.forEach(attr => clone.setAttribute(attr.clone()));
if (deep) {
this.children.forEach(child => clone.appendChild(child.clone(true)));
}
return clone;
}
/**
* Walks on each descendant node and invokes given `fn` function on it.
* The function receives two arguments: the node itself and its depth level
* from current node. If function returns `false`, it stops walking
* @param {Function} fn
*/
walk(fn, _level) {
_level = _level || 0;
let ctx = this.firstChild;
while (ctx) {
// in case if context node will be detached during `fn` call
const next = ctx.next;
if (fn(ctx, _level) === false || ctx.walk(fn, _level + 1) === false) {
return false;
}
ctx = next;
}
}
/**
* A helper method for transformation chaining: runs given `fn` function on
* current node and returns the same node
*/
use(fn) {
const args = [this];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
fn.apply(null, args);
return this;
}
toString() {
const attrs = this.attributes.map(attr => {
attr = this.getAttribute(attr.name);
const opt = attr.options;
let out = `${opt && opt.implied ? '!' : ''}${attr.name || ''}`;
if (opt && opt.boolean) {
out += '.';
} else if (attr.value != null) {
out += `="${attr.value}"`;
}
return out;
});
let out = `${this.name || ''}`;
if (attrs.length) {
out += `[${attrs.join(' ')}]`;
}
if (this.value != null) {
out += `{${this.value}}`;
}
if (this.selfClosing) {
out += '/';
}
if (this.repeat) {
out += `*${this.repeat.count ? this.repeat.count : ''}`;
if (this.repeat.value != null) {
out += `@${this.repeat.value}`;
}
}
return out;
}
};
/**
* Attribute factory
* @param {String|Attribute|Object} name Attribute name or attribute descriptor
* @param {*} value Attribute value
* @return {Attribute}
*/
function createAttribute(name, value) {
if (name instanceof Attribute) {
return name;
}
if (typeof name === 'string') {
return new Attribute(name, value);
}
if (name && typeof name === 'object') {
return new Attribute(name.name, name.value, name.options);
}
}
/**
* @param {String} str
* @return {String}
*/
function normalize(str) {
return String(str).trim();
}
function uniqueClass(item, i, arr) {
return item && arr.indexOf(item) === i;
}
/* harmony default export */ var node_es = (Node);
// CONCATENATED MODULE: ../node_modules/@emmetio/abbreviation/dist/abbreviation.es.js
const ASTERISK = 42; // *
/**
* Consumes node repeat token from current stream position and returns its
* parsed value
* @param {StringReader} stream
* @return {Object}
*/
function consumeRepeat(stream) {
if (stream.eat(ASTERISK)) {
stream.start = stream.pos;
// XXX think about extending repeat syntax with through numbering
return { count: stream.eatWhile(isNumber) ? +stream.current() : null };
}
}
const opt = { throws: true };
/**
* Consumes quoted literal from current stream position and returns it’s inner,
* unquoted, value
* @param {StringReader} stream
* @return {String} Returns `null` if unable to consume quoted value from current
* position
*/
function consumeQuoted(stream) {
if (eatQuoted(stream, opt)) {
return stream.current().slice(1, -1);
}
}
const TEXT_START = 123; // {
const TEXT_END = 125; // }
const abbreviation_es_ESCAPE = 92; // \ character
/**
* Consumes text node `{...}` from stream
* @param {StreamReader} stream
* @return {String} Returns consumed text value (without surrounding braces) or
* `null` if there’s no text at starting position
*/
function consumeText(stream) {
// NB using own implementation instead of `eatPair()` from @emmetio/stream-reader-utils
// to disable quoted value consuming
const start = stream.pos;
if (stream.eat(TEXT_START)) {
let stack = 1,
ch;
let result = '';
let offset = stream.pos;
while (!stream.eof()) {
ch = stream.next();
if (ch === TEXT_START) {
stack++;
} else if (ch === TEXT_END) {
stack--;
if (!stack) {
stream.start = start;
return result + stream.substring(offset, stream.pos - 1);
}
} else if (ch === abbreviation_es_ESCAPE) {
ch = stream.next();
if (ch === TEXT_START || ch === TEXT_END) {
result += stream.substring(offset, stream.pos - 2) + String.fromCharCode(ch);
offset = stream.pos;
}
}
}
// If we’re here then paired character can’t be consumed
stream.pos = start;
throw stream.error(`Unable to find closing ${String.fromCharCode(TEXT_END)} for text start`);
}
return null;
}
const EXCL = 33; // .
const DOT = 46; // .
const abbreviation_es_EQUALS = 61; // =
const ATTR_OPEN = 91; // [
const ATTR_CLOSE = 93; // ]
const reAttributeName = /^\!?[\w\-:\$@]+\.?$/;
/**
* Consumes attributes defined in square braces from given stream.
* Example:
* [attr col=3 title="Quoted string" selected. support={react}]
* @param {StringReader} stream
* @returns {Array} Array of consumed attributes
*/
function consumeAttributes(stream) {
if (!stream.eat(ATTR_OPEN)) {
return null;
}
const result = [];
let token, attr;
while (!stream.eof()) {
stream.eatWhile(isWhiteSpace);
if (stream.eat(ATTR_CLOSE)) {
return result; // End of attribute set
} else if ((token = consumeQuoted(stream)) != null) {
// Consumed quoted value: anonymous attribute
result.push({
name: null,
value: token
});
} else if (eatUnquoted(stream)) {
// Consumed next word: could be either attribute name or unquoted default value
token = stream.current();
if (!reAttributeName.test(token)) {
// anonymous attribute
result.push({ name: null, value: token });
} else {
// Looks like a regular attribute
attr = parseAttributeName(token);
result.push(attr);
if (stream.eat(abbreviation_es_EQUALS)) {
// Explicitly defined value. Could be a word, a quoted string
// or React-like expression
if ((token = consumeQuoted(stream)) != null) {
attr.value = token;
} else if ((token = consumeText(stream)) != null) {
attr.value = token;
attr.options = {
before: '{',
after: '}'
};
} else if (eatUnquoted(stream)) {
attr.value = stream.current();
}
}
}
} else {
throw stream.error('Expected attribute name');
}
}
throw stream.error('Expected closing "]" brace');
}
function parseAttributeName(name) {
const options = {};
// If a first character in attribute name is `!` — it’s an implied
// default attribute
if (name.charCodeAt(0) === EXCL) {
name = name.slice(1);
options.implied = true;
}
// Check for last character: if it’s a `.`, user wants boolean attribute
if (name.charCodeAt(name.length - 1) === DOT) {
name = name.slice(0, name.length - 1);
options.boolean = true;
}
const attr = { name };
if (Object.keys(options).length) {
attr.options = options;
}
return attr;
}
/**
* Eats token that can be an unquoted value from given stream
* @param {StreamReader} stream
* @return {Boolean}
*/
function eatUnquoted(stream) {
const start = stream.pos;
if (stream.eatWhile(isUnquoted)) {
stream.start = start;
return true;
}
}
function isUnquoted(code) {
return !isSpace(code) && !isQuote(code) && code !== ATTR_OPEN && code !== ATTR_CLOSE && code !== abbreviation_es_EQUALS;
}
const HASH = 35; // #
const DOT$1 = 46; // .
const abbreviation_es_SLASH = 47; // /
/**
* Consumes a single element node from current abbreviation stream
* @param {StringReader} stream
* @return {Node}
*/
function consumeElement(stream) {
// consume element name, if provided
const start = stream.pos;
const node = new node_es(eatName(stream));
let next;
while (!stream.eof()) {
if (stream.eat(DOT$1)) {
node.addClass(eatName(stream));
} else if (stream.eat(HASH)) {
node.setAttribute('id', eatName(stream));
} else if (stream.eat(abbreviation_es_SLASH)) {
// A self-closing indicator must be at the end of non-grouping node
if (node.isGroup) {
stream.backUp(1);
throw stream.error('Unexpected self-closing indicator');
}
node.selfClosing = true;
if (next = consumeRepeat(stream)) {
node.repeat = next;
}
break;
} else if (next = consumeAttributes(stream)) {
for (let i = 0, il = next.length; i < il; i++) {
node.setAttribute(next[i]);
}
} else if ((next = consumeText(stream)) !== null) {
node.value = next;
} else if (next = consumeRepeat(stream)) {
node.repeat = next;
} else {
break;
}
}
if (start === stream.pos) {
throw stream.error(`Unable to consume abbreviation node, unexpected ${stream.peek()}`);
}
return node;
}
function eatName(stream) {
stream.start = stream.pos;
stream.eatWhile(isName);
return stream.current();
}
function isName(code) {
return isAlphaNumeric(code) || code === 45 /* - */
|| code === 58 /* : */
|| code === 36 /* $ */
|| code === 64 /* @ */
|| code === 33 /* ! */
|| code === 95 /* _ */
|| code === 37 /* % */;
}
const GROUP_START = 40; // (
const GROUP_END = 41; // )
const OP_SIBLING = 43; // +
const OP_CHILD = 62; // >
const OP_CLIMB = 94; // ^
/**
* Parses given string into a node tree
* @param {String} str Abbreviation to parse
* @return {Node}
*/
function abbreviation_es_parse(str) {
const stream = new stream_reader_es(str.trim());
const root = new node_es();
let ctx = root,
groupStack = [],
ch;
while (!stream.eof()) {
ch = stream.peek();
if (ch === GROUP_START) {
// start of group
// The grouping node should be detached to properly handle
// out-of-bounds `^` operator. Node will be attached right on group end
const node = new node_es();
groupStack.push([node, ctx, stream.pos]);
ctx = node;
stream.next();
continue;
} else if (ch === GROUP_END) {
// end of group
const lastGroup = groupStack.pop();
if (!lastGroup) {
throw stream.error('Unexpected ")" group end');
}
const node = lastGroup[0];
ctx = lastGroup[1];
stream.next();
// a group can have a repeater
if (node.repeat = consumeRepeat(stream)) {
ctx.appendChild(node);
} else {
// move all children of group into parent node
while (node.firstChild) {
ctx.appendChild(node.firstChild);
}
}
// for convenience, groups can be joined with optional `+` operator
stream.eat(OP_SIBLING);
continue;
}
const node = consumeElement(stream);
ctx.appendChild(node);
if (stream.eof()) {
break;
}
switch (stream.peek()) {
case OP_SIBLING:
stream.next();
continue;
case OP_CHILD:
stream.next();
ctx = node;
continue;
case OP_CLIMB:
// it’s perfectly valid to have multiple `^` operators
while (stream.eat(OP_CLIMB)) {
ctx = ctx.parent || ctx;
}
continue;
}
}
if (groupStack.length) {
stream.pos = groupStack.pop()[2];
throw stream.error('Expected group close');
}
return root;
}
/**
* Parses given abbreviation and un-rolls it into a full tree: recursively
* replaces repeated elements with actual nodes
* @param {String} abbr
* @return {Node}
*/
function index(abbr) {
const tree = abbreviation_es_parse(abbr);
tree.walk(unroll);
return tree;
}
function unroll(node) {
if (!node.repeat || !node.repeat.count) {
return;
}
const parent = node.parent;
let ix = parent.children.indexOf(node);
for (let i = 0; i < node.repeat.count; i++) {
const clone = node.clone(true);
clone.repeat.value = i + 1;
clone.walk(unroll);
if (clone.isGroup) {
while (clone.children.length > 0) {
clone.firstChild.repeat = clone.repeat;
parent.insertAt(clone.firstChild, ix++);
}
} else {
parent.insertAt(clone, ix++);
}
}
node.parent.removeChild(node);
}
/* harmony default export */ var abbreviation_es = (index);
//# sourceMappingURL=abbreviation.es.js.map
// CONCATENATED MODULE: ../node_modules/@emmetio/html-snippets-resolver/dist/html-snippets-resolver.es.js
var html_snippets_resolver_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* For every node in given `tree`, finds matching snippet from `registry` and
* resolves it into a parsed abbreviation. Resolved node is then updated or
* replaced with matched abbreviation tree.
*
* A HTML registry basically contains aliases to another Emmet abbreviations,
* e.g. a predefined set of name, attribues and so on, possibly a complex
* abbreviation with multiple elements. So we have to get snippet, parse it
* and recursively resolve it.
*
* @param {Node} tree Parsed Emmet abbreviation
* @param {SnippetsRegistry} registry Registry with all available snippets
* @return {Node} Updated tree
*/
var html_snippets_resolver_es_index = function (tree, registry) {
tree.walk(node => resolveNode(node, registry));
return tree;
};
function resolveNode(node, registry) {
const stack = new Set();
const resolve = node => {
const snippet = registry.resolve(node.name);
// A snippet in stack means circular reference.
// It can be either a user error or a perfectly valid snippet like
// "img": "img[src alt]/", e.g. an element with predefined shape.
// In any case, simply stop parsing and keep element as is
if (!snippet || stack.has(snippet)) {
return;
}
// In case if matched snippet is a function, pass control into it
if (typeof snippet.value === 'function') {
return snippet.value(node, registry, resolve);
}
const tree = abbreviation_es(snippet.value);
stack.add(snippet);
tree.walk(resolve);
stack.delete(snippet);
// move current node contents into new tree
const childTarget = findDeepestNode(tree);
merge(childTarget, node);
while (tree.firstChild) {
node.parent.insertBefore(tree.firstChild, node);
}
childTarget.parent.insertBefore(node, childTarget);
childTarget.remove();
};
resolve(node);
}
/**
* Adds data from first node into second node and returns it
* @param {Node} from
* @param {Node} to
* @return {Node}
*/
function merge(from, to) {
to.name = from.name;
if (from.selfClosing) {
to.selfClosing = true;
}
if (from.value != null) {
to.value = from.value;
}
if (from.repeat) {
to.repeat = html_snippets_resolver_es__extends({}, from.repeat);
}
return mergeAttributes(from, to);
}
/**
* Transfer attributes from first element to second one and preserve first
* element’s attributes order
* @param {Node} from
* @param {Node} to
* @return {Node}
*/
function mergeAttributes(from, to) {
mergeClassNames(from, to);
// It’s important to preserve attributes order: ones in `from` have higher
// pripority than in `to`. Collect attributes in map in order they should
// appear in `to`
const attrMap = new Map();
let attrs = from.attributes;
for (let i = 0; i < attrs.length; i++) {
attrMap.set(attrs[i].name, attrs[i].clone());
}
attrs = to.attributes.slice();
for (let i = 0, attr, a; i < attrs.length; i++) {
attr = attrs[i];
if (attrMap.has(attr.name)) {
a = attrMap.get(attr.name);
a.value = attr.value;
// If user explicitly wrote attribute in abbreviation, it’s no longer
// implied and should be outputted even if value is empty
if (a.options.implied) {
a.options.implied = false;
}
} else {
attrMap.set(attr.name, attr);
}
to.removeAttribute(attr);
}
const newAttrs = Array.from(attrMap.values());
for (let i = 0; i < newAttrs.length; i++) {
to.setAttribute(newAttrs[i]);
}
return to;
}
/**
* Adds class names from first node to second one
* @param {Node} from
* @param {Node} to
* @return {Node}
*/
function mergeClassNames(from, to) {
const classNames = from.classList;
for (let i = 0; i < classNames.length; i++) {
to.addClass(classNames[i]);
}
return to;
}
/**
* Finds node which is the deepest for in current node or node iteself.
* @param {Node} node
* @return {Node}
*/
function findDeepestNode(node) {
while (node.children.length) {
node = node.children[node.children.length - 1];
}
return node;
}
/* harmony default export */ var html_snippets_resolver_es = (html_snippets_resolver_es_index);
// CONCATENATED MODULE: ../node_modules/@emmetio/implicit-tag/dist/implicit-tag.es.js
const inlineElements = new Set('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(','));
const 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'
};
/**
* Returns best child node name for given parent node name
* @param {String} parentName Name of parent node
* @return {String}
*/
function resolveImplicitName(parentName) {
parentName = (parentName || '').toLowerCase();
return elementMap[parentName] || (inlineElements.has(parentName) ? 'span' : 'div');
}
/* harmony default export */ var implicit_tag_es = (resolveImplicitName);
// CONCATENATED MODULE: ../node_modules/@emmetio/html-transform/dist/html-transform.es.js
var html_transform_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* Adds missing tag names for given tree depending on node’s parent name
*/
var implicitTags = function (tree) {
tree.walk(node => {
// resolve only nameless nodes without content
if (node.name == null && node.attributes.length) {
node.name = implicit_tag_es(node.parent.name);
}
});
return tree;
};
/**
* Locates all occurances of given `token` which are not escaped (e.g. are not
* preceded with `\`) given in `str`
* @param {String} str
* @return {Array} Array of token ranges
*/
function findUnescapedTokens(str, token) {
const result = new Set();
const tlen = token.length;
// 1. Find all occurances of tokens
let pos = 0;
while ((pos = str.indexOf(token, pos)) !== -1) {
result.add(pos);
pos += tlen;
}
if (result.size) {
// 2. Remove ones that escaped
let pos = 0;
const len = str.length;
while (pos < len) {
if (str[pos++] === '\\') {
result.delete(pos++);
}
}
}
return Array.from(result).map(ix => html_transform_es_range(ix, tlen));
}
/**
* Replaces `ranges`, generated by `range()` function, with given `value` in `str`
* @param {String} str Where to replace ranges
* @param {Array} ranges Ranes, created by `range()` function
* @param {String|Function} value Replacement value. If it’s a function, it
* will take a range value as argument and should return a new string
* @return {String}
*/
function replaceRanges(str, ranges, value) {
// should walk from the end of array to keep ranges valid after replacement
for (let i = ranges.length - 1; i >= 0; i--) {
const r = ranges[i];
let offset = 0;
let offsetLength = 0;
let descendingOrder = false;
if (str.substr(r[0] + r[1], 1) === '@') {
if (str.substr(r[0] + r[1] + 1, 1) === '-') {
descendingOrder = true;
}
const matches = str.substr(r[0] + r[1] + 1 + Number(descendingOrder)).match(/^(\d+)/);
if (matches) {
offsetLength = matches[1].length + 1 + Number(descendingOrder);
offset = parseInt(matches[1]) - 1;
} else {
offsetLength = 2;
}
}
str = str.substring(0, r[0]) + (typeof value === 'function' ? value(str.substr(r[0], r[1]), offset, descendingOrder) : value) + str.substring(r[0] + r[1] + offsetLength);
}
return str;
}
function html_transform_es_range(start, length) {
return [start, length];
}
const numberingToken = '$';
/**
* Numbering of expanded abbreviation: finds all nodes with `$` in value
* or attributes and replaces its occurances with repeater value
*/
var applyNumbering = function (tree) {
tree.walk(applyNumbering$1);
return tree;
};
/**
* Applies numbering for given node: replaces occurances of numbering token
* in node’s name, content and attributes
* @param {Node} node
* @return {Node}
*/
function applyNumbering$1(node) {
const repeater = findRepeater(node);
if (repeater && repeater.value != null) {
// NB replace numbering in nodes with explicit repeater only:
// it solves issues with abbreviations like `xsl:if[test=$foo]` where
// `$foo` is preferred output
const value = repeater.value;
const count = repeater.count;
node.name = replaceNumbering(node.name, value, count);
node.value = replaceNumbering(node.value, value, count);
node.attributes.forEach(attr => {
const copy = node.getAttribute(attr.name).clone();
copy.name = replaceNumbering(attr.name, value, count);
copy.value = replaceNumbering(attr.value, value, count);
node.replaceAttribute(attr.name, copy);
});
}
return node;
}
/**
* Returns repeater object for given node
* @param {Node} node
* @return {Object}
*/
function findRepeater(node) {
while (node) {
if (node.repeat) {
return node.repeat;
}
node = node.parent;
}
}
/**
* Replaces numbering in given string
* @param {String} str
* @param {Number} value
* @return {String}
*/
function replaceNumbering(str, value, count) {
// replace numbering in strings only: skip explicit wrappers that could
// contain unescaped numbering tokens
if (typeof str === 'string') {
const ranges = getNumberingRanges(str);
return replaceNumberingRanges(str, ranges, value, count);
}
return str;
}
/**
* Returns numbering ranges, e.g. ranges of `$` occurances, in given string.
* Multiple adjacent ranges are combined
* @param {String} str
* @return {Array}
*/
function getNumberingRanges(str) {
return findUnescapedTokens(str || '', numberingToken).reduce((out, range$$1) => {
// skip ranges that actually belongs to output placeholder or tabstops
if (!/[#{]/.test(str[range$$1[0] + 1] || '')) {
const lastRange = out[out.length - 1];
if (lastRange && lastRange[0] + lastRange[1] === range$$1[0]) {
lastRange[1] += range$$1[1];
} else {
out.push(range$$1);
}
}
return out;
}, []);
}
/**
* @param {String} str
* @param {Array} ranges
* @param {Number} value
* @return {String}
*/
function replaceNumberingRanges(str, ranges, value, count) {
const replaced = replaceRanges(str, ranges, (token, offset, descendingOrder) => {
let _value = descendingOrder ? String(offset + count - value + 1) : String(value + offset);
// pad values for multiple numbering tokens, e.g. 3 for $$$ becomes 003
while (_value.length < token.length) {
_value = '0' + _value;
}
return _value;
});
// unescape screened numbering tokens
return unescapeString(replaced);
}
/**
* Unescapes characters, screened with `\`, in given string
* @param {String} str
* @return {String}
*/
function unescapeString(str) {
let i = 0,
result = '';
const len = str.length;
while (i < len) {
const ch = str[i++];
result += ch === '\\' ? str[i++] || '' : ch;
}
return result;
}
/** Placeholder for inserted content */
const placeholder = '$#';
/** Placeholder for caret */
const caret = '|';
const reUrl = /^((?:https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
const reEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
const reProto = /^([a-z]+:)?\/\//i;
/**
* Inserts content into node with implicit repeat count: this node is then
* duplicated for each content item and content itself is inserted either into
* deepest child or instead of a special token.
*
* This method uses two distinct steps: `prepare()` and `insert()` since most
* likely these steps will be used separately to properly insert content
* with unescaped `$` numbering markers.
*
* @param {Node} tree Parsed abbreviation
* @param {String[]} content Array of content items to insert
* @return {Node}
*/
/**
* Finds nodes with implicit repeat and creates `amount` copies of it in tree
* @param {Node} tree
* @param {Number} amount
* @return {Node}
*/
function prepare(tree, amount) {
amount = amount || 1;
tree.walk(node => {
if (node.repeat && node.repeat.count === null) {
for (let i = 0; i < amount; i++) {
const clone = node.clone(true);
clone.repeat.implicit = true;
clone.repeat.count = amount;
clone.repeat.value = i + 1;
clone.repeat.index = i;
node.parent.insertBefore(clone, node);
}
node.remove();
}
});
return tree;
}
/**
* Inserts content into implicitly repeated nodes, created by `prepare()` method
* @param {Node} tree
* @param {String[]} content
* @return {Node}
*/
function insert(tree, content) {
if (Array.isArray(content) && content.length) {
let updated = false;
tree.walk(node => {
if (node.repeat && node.repeat.implicit) {
updated = true;
insertContent(node, content[node.repeat.index]);
}
});
if (!updated) {
// no node with implicit repeat was found, insert content as
// deepest child
setNodeContent(html_transform_es_findDeepestNode(tree), content.join('\n'));
}
}
return tree;
}
/**
* Inserts `content` into given `node`: either replaces output placeholders
* or inserts it into deepest child node
* @param {Node} node
* @param {String} content
* @return {Node}
*/
function insertContent(node, content) {
let inserted = insertContentIntoPlaceholder(node, content);
node.walk(child => inserted |= insertContentIntoPlaceholder(child, content));
if (!inserted) {
// no placeholders were found in node, insert content into deepest child
setNodeContent(html_transform_es_findDeepestNode(node), content);
}
return node;
}
/**
* Inserts given `content` into placeholders for given `node`. Placeholders
* might be available in attribute values and node content
* @param {Node} node
* @param {String} content
* @return {Boolean} Returns `true` if placeholders were found and replaced in node
*/
function insertContentIntoPlaceholder(node, content) {
const state = { replaced: false };
node.value = replacePlaceholder(node.value, content, state);
node.attributes.forEach(attr => {
if (attr.value) {
node.setAttribute(attr.name, replacePlaceholder(attr.value, content, state));
}
});
return state.replaced;
}
/**
* Replaces all placeholder occurances in given `str` with `value`
* @param {String} str
* @param {String} value
* @param {Object} [_state] If provided, set `replaced` property of given
* object to `true` if placeholder was found and replaced
* @return {String}
*/
function replacePlaceholder(str, value, _state) {
if (typeof str === 'string') {
const ranges = findUnescapedTokens(str, placeholder);
if (ranges.length) {
if (_state) {
_state.replaced = true;
}
str = replaceRanges(str, ranges, value);
}
}
return str;
}
/**
* Finds node which is the deepest for in current node or node iteself.
* @param {Node} node
* @return {Node}
*/
function html_transform_es_findDeepestNode(node) {
while (node.children.length) {
node = node.children[node.children.length - 1];
}
return node;
}
/**
* Updates content of given node
* @param {Node} node
* @param {String} content
*/
function setNodeContent(node, content) {
// find caret position and replace it with content, if possible
if (node.value) {
const ranges = findUnescapedTokens(node.value, caret);
if (ranges.length) {
node.value = replaceRanges(node.value, ranges, content);
return;
}
}
if (node.name.toLowerCase() === 'a' || node.hasAttribute('href')) {
// special case: inserting content into `` tag
if (reUrl.test(content)) {
node.setAttribute('href', (reProto.test(content) ? '' : 'http://') + content);
} else if (reEmail.test(content)) {
node.setAttribute('href', 'mailto:' + content);
}
}
node.value = content;
}
const html_transform_es_defaultOptions = {
element: '__',
modifier: '_'
};
const reElement = /^(-+)([a-z0-9]+[a-z0-9-]*)/i;
const reModifier = /^(_+)([a-z0-9]+[a-z0-9-_]*)/i;
const blockCandidates1 = className => /^[a-z]\-/i.test(className);
const blockCandidates2 = className => /^[a-z]/i.test(className);
/**
* BEM transformer: updates class names written as `-element` and
* `_modifier` into full class names as described in BEM specs. Also adds missing
* class names: fir example, if node contains `.block_modifier` class, ensures
* that element contains `.block` class as well
*/
var bem = function (tree, options) {
options = html_transform_es__extends({}, html_transform_es_defaultOptions, options);
tree.walk(node => expandClassNames(node, options));
const lookup = createBlockLookup(tree);
tree.walk(node => expandShortNotation(node, lookup, options));
return tree;
};
/**
* Expands existing class names in BEM notation in given `node`.
* For example, if node contains `b__el_mod` class name, this method ensures
* that element contains `b__el` class as well
* @param {Node} node
* @param {Object} options
* @return {Set}
*/
function expandClassNames(node, options) {
const classNames = node.classList.reduce((out, cl) => {
// remove all modifiers and element prefixes from class name to get a base element name
const ix = cl.indexOf('_');
if (ix > 0 && !cl.startsWith('-')) {
out.add(cl.slice(0, ix));
out.add(cl.slice(ix));
return out;
}
return out.add(cl);
}, new Set());
if (classNames.size) {
node.setAttribute('class', Array.from(classNames).join(' '));
}
}
/**
* Expands short BEM notation, e.g. `-element` and `_modifier`
* @param {Node} node Parsed Emmet abbreviation node
* @param {Map} lookup BEM block name lookup
* @param {Object} options
*/
function expandShortNotation(node, lookup, options) {
const classNames = node.classList.reduce((out, cl) => {
let prefix, m;
const originalClass = cl;
// parse element definition (could be only one)
if (m = cl.match(reElement)) {
prefix = getBlockName(node, lookup, m[1]) + options.element + m[2];
out.add(prefix);
cl = cl.slice(m[0].length);
}
// parse modifiers definitions
if (m = cl.match(reModifier)) {
if (!prefix) {
prefix = getBlockName(node, lookup, m[1]);
out.add(prefix);
}
out.add(`${prefix}${options.modifier}${m[2]}`);
cl = cl.slice(m[0].length);
}
if (cl === originalClass) {
// class name wasn’t modified: it’s not a BEM-specific class,
// add it as-is into output
out.add(originalClass);
}
return out;
}, new Set());
const arrClassNames = Array.from(classNames).filter(Boolean);
if (arrClassNames.length) {
node.setAttribute('class', arrClassNames.join(' '));
}
}
/**
* Creates block name lookup for each node in given tree, e.g. finds block
* name explicitly for each node
* @param {Node} tree
* @return {Map}
*/
function createBlockLookup(tree) {
const lookup = new Map();
tree.walk(node => {
const classNames = node.classList;
if (classNames.length) {
// guess best block name from class or use parent’s block name
lookup.set(node, find(classNames, blockCandidates1) || find(classNames, blockCandidates2) || lookup.get(node.parent));
}
});
return lookup;
}
/**
* Returns block name for given `node` by `prefix`, which tells the depth of
* of parent node lookup
* @param {Node} node
* @param {Map} lookup
* @param {String} prefix
* @return {String}
*/
function getBlockName(node, lookup, prefix) {
let depth = prefix.length > 1 ? prefix.length : 0;
// NB don’t walk up to root node, stay at first root child in case of
// too deep prefix
while (node.parent && node.parent.parent && depth--) {
node = node.parent;
}
return lookup.get(node) || '';
}
function find(arr, filter) {
for (let i = 0; i < arr.length; i++) {
if (reElement.test(arr[i]) || reModifier.test(arr[i])) {
break;
}
if (filter(arr[i])) {
return arr[i];
}
}
}
/**
* JSX transformer: replaces `class` and `for` attributes with `className` and
* `htmlFor` attributes respectively
*/
var jsx = function (tree) {
tree.walk(node => {
replace(node, 'class', 'className');
replace(node, 'for', 'htmlFor');
});
return tree;
};
function replace(node, oldName, newName) {
let attr = node.getAttribute(oldName);
if (attr) {
attr.name = newName;
}
}
const reSupporterNames = /^xsl:(variable|with\-param)$/i;
/**
* XSL transformer: removes `select` attributes from certain nodes that contain
* children
*/
var xsl = function (tree) {
tree.walk(node => {
if (reSupporterNames.test(node.name || '') && (node.children.length || node.value)) {
node.removeAttribute('select');
}
});
return tree;
};
const supportedAddons = { bem, jsx, xsl };
/**
* Runs additional transforms on given tree.
* These transforms may introduce side-effects and unexpected result
* so they are not applied by default, authors must specify which addons
* in `addons` argument as `{addonName: addonOptions}`
* @param {Node} tree Parsed Emmet abbreviation
* @param {Object} addons Add-ons to apply and their options
*/
var addons = function (tree, addons) {
Object.keys(addons || {}).forEach(key => {
if (key in supportedAddons) {
const addonOpt = typeof addons[key] === 'object' ? addons[key] : null;
tree = tree.use(supportedAddons[key], addonOpt);
}
});
return tree;
};
/**
* Applies basic HTML-specific transformations for given parsed abbreviation:
* – resolve implied tag names
* – insert repeated content
* – resolve node numbering
*/
var html_transform_es_index = function (tree, content, appliedAddons) {
if (typeof content === 'string') {
content = [content];
} else if (content && typeof content === 'object' && !Array.isArray(content)) {
appliedAddons = content;
content = null;
}
return tree.use(implicitTags).use(prepare, Array.isArray(content) ? content.length : null).use(applyNumbering).use(insert, content).use(addons, appliedAddons);
};
/* harmony default export */ var html_transform_es = (html_transform_es_index);
// CONCATENATED MODULE: ../node_modules/@emmetio/variable-resolver/dist/variable-resolver.es.js
/**
* Replaces all unescaped ${variable} occurances in given parsed abbreviation
* `tree` with values provided in `variables` hash. Precede `$` with `\` to
* escape it and skip replacement
* @param {Node} tree Parsed abbreviation tree
* @param {Object} variables Variables values
* @return {Node}
*/
function replaceVariables(tree, variables) {
variables = variables || {};
tree.walk(node => replaceInNode(node, variables));
return tree;
}
function replaceInNode(node, variables) {
// Replace variables in attributes.
const attrs = node.attributes;
for (let i = 0, il = attrs.length; i < il; i++) {
const attr = attrs[i];
if (typeof attr.value === 'string') {
node.setAttribute(attr.name, replaceInString(attr.value, variables));
}
}
if (node.value != null) {
node.value = replaceInString(node.value, variables);
}
return node;
}
/**
* Replaces all unescaped `${variable}` occurances in given string with values
* from `variables` object
* @param {String} string
* @param {Object} variables
* @return {String}
*/
function replaceInString(string, variables) {
const model = createModel(string);
let offset = 0;
let output = '';
for (let i = 0, il = model.variables.length; i < il; i++) {
const v = model.variables[i];
let value = v.name in variables ? variables[v.name] : v.name;
if (typeof value === 'function') {
value = value(model.string, v, offset + v.location);
}
output += model.string.slice(offset, v.location) + value;
offset = v.location + v.length;
}
return output + model.string.slice(offset);
}
/**
* Creates variable model from given string. The model contains a `string` with
* all escaped variable tokens written without escape symbol and `variables`
* property with all unescaped variables and their ranges
* @param {String} string
* @return {Object}
*/
function createModel(string) {
const reVariable = /\$\{([a-z][\w\-]*)\}/ig;
const escapeCharCode = 92; // `\` symbol
const variables = [];
// We have to replace unescaped (e.g. not preceded with `\`) tokens.
// Instead of writing a stream parser, we’ll cut some edges here:
// 1. Find all tokens
// 2. Walk string char-by-char and resolve only tokens that are not escaped
const tokens = new Map();
let m;
while (m = reVariable.exec(string)) {
tokens.set(m.index, m);
}
if (tokens.size) {
let start = 0,
pos = 0,
len = string.length;
let output = '';
while (pos < len) {
if (string.charCodeAt(pos) === escapeCharCode && tokens.has(pos + 1)) {
// Found escape symbol that escapes variable: we should
// omit this symbol in output string and skip variable
const token = tokens.get(pos + 1);
output += string.slice(start, pos) + token[0];
start = pos = token.index + token[0].length;
tokens.delete(pos + 1);
continue;
}
pos++;
}
string = output + string.slice(start);
// Not using `.map()` here to reduce memory allocations
const validMatches = Array.from(tokens.values());
for (let i = 0, il = validMatches.length; i < il; i++) {
const token = validMatches[i];
variables.push({
name: token[1],
location: token.index,
length: token[0].length
});
}
}
return { string, variables };
}
/* harmony default export */ var variable_resolver_es = (replaceVariables);
// CONCATENATED MODULE: ../node_modules/@emmetio/output-renderer/dist/output-renderer.es.js
var output_renderer_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
const defaultFieldsRenderer = text => text;
/**
* Output node is an object containing generated output for given Emmet
* abbreviation node. Output node can be passed to various processors that
* may shape-up final node output. The final output is simply a concatenation
* of `.open`, `.text` and `.close` properties and its `.before*` and `.after*`
* satellites
* @param {Node} node Parsed Emmet abbreviation node
* @param {Function} fieldsRenderer A function for rendering fielded text (text with
* tabstops) for current node. @see ./render.js for details
*/
let OutputNode = class OutputNode {
constructor(node, fieldsRenderer, options) {
if (typeof fieldsRenderer === 'object') {
options = fieldsRenderer;
fieldsRenderer = null;
}
this.node = node;
this._fieldsRenderer = fieldsRenderer || defaultFieldsRenderer;
this.open = null;
this.beforeOpen = '';
this.afterOpen = '';
this.close = null;
this.beforeClose = '';
this.afterClose = '';
this.text = null;
this.beforeText = '';
this.afterText = '';
this.indent = '';
this.newline = '';
if (options) {
output_renderer_es__extends(this, options);
}
}
clone() {
return new this.constructor(this.node, this);
}
/**
* Properly indents given multiline text
* @param {String} text
*/
indentText(text) {
const lines = splitByLines(text);
if (lines.length === 1) {
// no newlines, nothing to indent
return text;
}
// No newline and no indent means no formatting at all:
// in this case we should replace newlines with spaces
const nl = !this.newline && !this.indent ? ' ' : this.newline;
return lines.map((line, i) => i ? this.indent + line : line).join(nl);
}
/**
* Renders given text with fields
* @param {String} text
* @return {String}
*/
renderFields(text) {
return this._fieldsRenderer(text);
}
toString(children) {
const open = this._wrap(this.open, this.beforeOpen, this.afterOpen);
const close = this._wrap(this.close, this.beforeClose, this.afterClose);
const text = this._wrap(this.text, this.beforeText, this.afterText);
return open + text + (children != null ? children : '') + close;
}
_wrap(str, before, after) {
before = before != null ? before : '';
after = after != null ? after : '';
// automatically trim whitespace for non-empty wraps
if (str != null) {
str = before ? str.replace(/^\s+/, '') : str;
str = after ? str.replace(/\s+$/, '') : str;
return before + this.indentText(str) + after;
}
return '';
}
};
/**
* Splits given text by lines
* @param {String} text
* @return {String[]}
*/
function splitByLines(text) {
return (text || '').split(/\r\n|\r|\n/g);
}
/**
* Default output of field (tabstop)
* @param {Number} index Field index
* @param {String} placeholder Field placeholder, can be null
* @return {String}
*/
const defaultField = (index, placeholder) => placeholder || '';
/**
* Renders given parsed abbreviation `tree` via `formatter` function.
* @param {Node} tree Parsed Emmet abbreviation
* @param {Function} [field] Optional function to format field/tabstop (@see `defaultField`)
* @param {Function} formatter Output formatter function. It takes an output node—
* a special wrapper for parsed node that holds formatting and output properties—
* and updates its output properties to shape-up node’s output.
* Function arguments:
* – `outNode`: OutputNode
* – `renderFields`: a helper function that parses fields/tabstops from given
* text and replaces them with `field` function output.
* It also takes care about field indicies and ensures that the same indicies
* from different nodes won’t collide
*/
function render(tree, field, formatter) {
if (typeof formatter === 'undefined') {
formatter = field;
field = null;
}
field = field || defaultField;
// Each node may contain fields like `${1:placeholder}`.
// Since most modern editors will link all fields with the same
// index, we have to ensure that different nodes has their own indicies.
// We’ll use this `fieldState` object to globally increment field indices
// during output
const fieldState = { index: 1 };
const fieldsRenderer = text => text == null ? field(fieldState.index++) : getFieldsModel(text, fieldState).mark(field);
return run(tree.children, formatter, fieldsRenderer);
}
function run(nodes, formatter, fieldsRenderer) {
return nodes.map(node => {
const outNode = formatter(new OutputNode(node, fieldsRenderer));
return outNode ? outNode.toString(run(node.children, formatter, fieldsRenderer)) : '';
}).join('');
}
/**
* Returns fields (tab-stops) model with properly updated indices that won’t
* collide with fields in other nodes of foprmatted tree
* @param {String|Object} text Text to get fields model from or model itself
* @param {Object} fieldState Abbreviation tree-wide field state reference
* @return {Object} Field model
*/
function getFieldsModel(text, fieldState) {
const model = typeof text === 'object' ? text : field_parser_es(text);
let largestIndex = -1;
model.fields.forEach(field => {
field.index += fieldState.index;
if (field.index > largestIndex) {
largestIndex = field.index;
}
});
if (largestIndex !== -1) {
fieldState.index = largestIndex + 1;
}
return model;
}
/* harmony default export */ var output_renderer_es = (render);
// CONCATENATED MODULE: ../node_modules/@emmetio/markup-formatters/dist/markup-formatters.es.js
var markup_formatters_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
const TOKEN = /^(.*?)([A-Z_]+)(.*?)$/;
const TOKEN_OPEN = 91; // [
const TOKEN_CLOSE = 93; // ]
/**
* A basic templating engine.
* Takes every `[TOKEN]` from given string and replaces it with
* `TOKEN` value from given `data` attribute. The token itself may contain
* various characters between `[`, token name and `]`. Contents of `[...]` will
* be outputted only if `TOKEN` value is not empty. Also, only `TOKEN` name will
* be replaced with actual value, all other characters will remain as is.
*
* Example:
* ```
* template('[from
and to
values
* @param {Number} from
* @param {Number} to
* @returns {Number}
*/
function rand(from, to) {
return Math.floor(Math.random() * (to - from) + from);
}
/**
* @param {Array} arr
* @param {Number} count
* @returns {Array}
*/
function sample(arr, count) {
const len = arr.length;
const iterations = Math.min(len, count);
const result = new Set();
while (result.size < iterations) {
result.add(arr[rand(0, len)]);
}
return Array.from(result);
}
function choice(val) {
return val[rand(0, val.length - 1)];
}
function sentence(words, end) {
if (words.length) {
words = [capitalize(words[0])].concat(words.slice(1));
}
return words.join(' ') + (end || choice('?!...')); // more dots than question marks
}
function capitalize(word) {
return word[0].toUpperCase() + word.slice(1);
}
/**
* Insert commas at randomly selected words. This function modifies values
* inside words
array
* @param {Array} words
*/
function insertCommas(words) {
if (words.length < 2) {
return words;
}
words = words.slice();
const len = words.length;
const hasComma = /,$/;
let totalCommas = 0;
if (len > 3 && len <= 6) {
totalCommas = rand(0, 1);
} else if (len > 6 && len <= 12) {
totalCommas = rand(0, 2);
} else {
totalCommas = rand(1, 4);
}
for (let i = 0, pos, word; i < totalCommas; i++) {
pos = rand(0, len - 2);
if (!hasComma.test(words[pos])) {
words[pos] += ',';
}
}
return words;
}
/**
* Generate a paragraph of "Lorem ipsum" text
* @param {Object} dict Words dictionary (see `lang/*.json`)
* @param {Number} wordCount Words count in paragraph
* @param {Boolean} startWithCommon Should paragraph start with common
* "lorem ipsum" sentence.
* @returns {String}
*/
function paragraph(dict, wordCount, startWithCommon) {
const result = [];
let totalWords = 0;
let words;
if (startWithCommon && dict.common) {
words = dict.common.slice(0, wordCount);
totalWords += words.length;
result.push(sentence(insertCommas(words), '.'));
}
while (totalWords < wordCount) {
words = sample(dict.words, Math.min(rand(2, 30), wordCount - totalWords));
totalWords += words.length;
result.push(sentence(insertCommas(words)));
}
return result.join(' ');
}
/**
* Check if given node is in repeating context, e.g. node itself or one of its
* parent is repeated
* @param {Node} node
* @return {Boolean}
*/
function isRepeating(node) {
while (node.parent) {
if (node.repeat && node.repeat.value && node.repeat.value > 1) {
return true;
}
node = node.parent;
}
return false;
}
/* harmony default export */ var lorem_es = (lorem_es_index);
// CONCATENATED MODULE: ../node_modules/@emmetio/snippets-registry/dist/snippets-registry.es.js
let Snippet = class Snippet {
constructor(key, value) {
this.key = key;
this.value = value;
}
};
let SnippetsStorage = class SnippetsStorage {
constructor(data) {
this._string = new Map();
this._regexp = new Map();
this._disabled = false;
this.load(data);
}
get disabled() {
return this._disabled;
}
/**
* Disables current store. A disabled store always returns `undefined`
* on `get()` method
*/
disable() {
this._disabled = true;
}
/**
* Enables current store.
*/
enable() {
this._disabled = false;
}
/**
* Registers a new snippet item
* @param {String|Regexp} key
* @param {String|Function} value
*/
set(key, value) {
if (typeof key === 'string') {
key.split('|').forEach(k => this._string.set(k, new Snippet(k, value)));
} else if (key instanceof RegExp) {
this._regexp.set(key, new Snippet(key, value));
} else {
throw new Error('Unknow snippet key: ' + key);
}
return this;
}
/**
* Returns a snippet matching given key. It first tries to find snippet
* exact match in a string key map, then tries to match one with regexp key
* @param {String} key
* @return {Snippet}
*/
get(key) {
if (this.disabled) {
return undefined;
}
if (this._string.has(key)) {
return this._string.get(key);
}
const keys = Array.from(this._regexp.keys());
for (let i = 0, il = keys.length; i < il; i++) {
if (keys[i].test(key)) {
return this._regexp.get(keys[i]);
}
}
}
/**
* Batch load of snippets data
* @param {Object|Map} data
*/
load(data) {
this.reset();
if (data instanceof Map) {
data.forEach((value, key) => this.set(key, value));
} else if (data && typeof data === 'object') {
Object.keys(data).forEach(key => this.set(key, data[key]));
}
}
/**
* Clears all stored snippets
*/
reset() {
this._string.clear();
this._regexp.clear();
}
/**
* Returns all available snippets from given store
*/
values() {
if (this.disabled) {
return [];
}
const string = Array.from(this._string.values());
const regexp = Array.from(this._regexp.values());
return string.concat(regexp);
}
};
/**
* A snippets registry. Contains snippets, separated by store and sorted by
* priority: a store with higher priority takes precedence when resolving snippet
* for given key
*/
let SnippetsRegistry = class SnippetsRegistry {
/**
* Creates snippets registry, filled with given `data`
* @param {Object|Array} data Registry snippets. If array is given, adds items
* from array in order of precedence, registers global snippets otherwise
*/
constructor(data) {
this._registry = [];
if (Array.isArray(data)) {
data.forEach((snippets, level) => this.add(level, snippets));
} else if (typeof data === 'object') {
this.add(data);
}
}
/**
* Return store for given level
* @param {Number} level
* @return {SnippetsStorage}
*/
get(level) {
for (let i = 0; i < this._registry.length; i++) {
const item = this._registry[i];
if (item.level === level) {
return item.store;
}
}
}
/**
* Adds new store for given level
* @param {Number} [level] Store level (priority). Store with higher level
* takes precedence when resolving snippets
* @param {Object} [snippets] A snippets data for new store
* @return {SnipetsStorage}
*/
add(level, snippets) {
if (level != null && typeof level === 'object') {
snippets = level;
level = 0;
}
const store = new SnippetsStorage(snippets);
// remove previous store from same level
this.remove(level);
this._registry.push({ level, store });
this._registry.sort((a, b) => b.level - a.level);
return store;
}
/**
* Remove registry with given level or store
* @param {Number|SnippetsStorage} data Either level or snippets store
*/
remove(data) {
this._registry = this._registry.filter(item => item.level !== data && item.store !== data);
}
/**
* Returns snippet from registry that matches given name
* @param {String} name
* @return {Snippet}
*/
resolve(name) {
for (let i = 0; i < this._registry.length; i++) {
const snippet = this._registry[i].store.get(name);
if (snippet) {
return snippet;
}
}
}
/**
* Returns all available snippets from current registry. Snippets with the
* same key are resolved by their storage priority.
* @param {Object} options
* @param {Object} options.type Return snippets only of given type: 'string'
* or 'regexp'. Returns all snippets if not defined
* @return {Array}
*/
all(options) {
options = options || {};
const result = new Map();
const fillResult = snippet => {
const type = snippet.key instanceof RegExp ? 'regexp' : 'string';
if ((!options.type || options.type === type) && !result.has(snippet.key)) {
result.set(snippet.key, snippet);
}
};
this._registry.forEach(item => {
item.store.values().forEach(fillResult);
});
return Array.from(result.values());
}
/**
* Removes all stores from registry
*/
clear() {
this._registry.length = 0;
}
};
/* harmony default export */ var snippets_registry_es = (SnippetsRegistry);
// CONCATENATED MODULE: ../node_modules/@emmetio/output-profile/dist/output-profile.es.js
var output_profile_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* @type {EmmetOutputProfile}
*/
var output_profile_es_defaultOptions = {
indent: '\t',
tagCase: '',
attributeCase: '',
attributeQuotes: 'double',
format: true,
formatSkip: ['html'],
formatForce: ['body'],
inlineBreak: 3,
compactBooleanAttributes: false,
booleanAttributes: ['contenteditable', 'seamless', 'async', 'autofocus', 'autoplay', 'checked', 'controls', 'defer', 'disabled', 'formnovalidate', 'hidden', 'ismap', 'loop', 'multiple', 'muted', 'novalidate', 'readonly', 'required', 'reversed', 'selected', 'typemustmatch'],
selfClosingStyle: 'html',
inlineElements: ['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']
};
/**
* Creates output profile for given options
*/
let Profile = class Profile {
/**
* @param {EmmetOutputProfile} options
*/
constructor(options) {
/** @type {EmmetOutputProfile} */
this.options = output_profile_es__extends({}, output_profile_es_defaultOptions, options);
this.quoteChar = this.options.attributeQuotes === 'single' ? '\'' : '"';
}
/**
* Returns value of given option name
* @param {String} name
* @return {*}
*/
get(name) {
return this.options[name];
}
/**
* Quote given string according to profile
* @param {String} str String to quote
* @return {String}
*/
quote(str) {
return `${this.quoteChar}${str != null ? str : ''}${this.quoteChar}`;
}
/**
* Output given tag name according to options
* @param {String} name
* @return {String}
*/
name(name) {
return strcase(name, this.options.tagCase);
}
/**
* Outputs attribute name according to current settings
* @param {String} attr Attribute name
* @return {String}
*/
attribute(attr) {
return strcase(attr, this.options.attributeCase);
}
/**
* Check if given attribute is boolean
* @param {Object} attr
* @return {Boolean}
*/
isBooleanAttribute(attr) {
return attr.options.boolean || this.get('booleanAttributes').indexOf((attr.name || '').toLowerCase()) !== -1;
}
/**
* Returns a token for self-closing tag, depending on current options
* @return {String}
*/
selfClose() {
switch (this.options.selfClosingStyle) {
case 'xhtml':
return ' /';
case 'xml':
return '/';
default:
return '';
}
}
/**
* Returns indent for given level
* @param {Number} level Indentation level
* @return {String}
*/
indent(level) {
level = level || 0;
let output = '';
while (level--) {
output += this.options.indent;
}
return output;
}
/**
* Check if given tag name belongs to inline-level element
* @param {Object|String} node Parsed node or tag name
* @return {Boolean}
*/
isInline(node) {
if (typeof node === 'string') {
return this.get('inlineElements').indexOf(node.toLowerCase()) !== -1;
}
// inline node is a node either with inline-level name or text-only node
return node.name != null ? this.isInline(node.name) : node.isTextOnly;
}
/**
* Outputs formatted field for given params
* @param {Number} index Field index
* @param {String} [placeholder] Field placeholder, can be empty
* @return {String}
*/
field(index, placeholder) {
return this.options.field(index, placeholder);
}
};
function strcase(string, type) {
if (type) {
return type === 'upper' ? string.toUpperCase() : string.toLowerCase();
}
return string;
}
/* harmony default export */ var output_profile_es = (Profile);
// CONCATENATED MODULE: ../node_modules/@emmetio/expand-abbreviation/dist/expand.es.js
var expand_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
/**
* Expands given abbreviation into code
* @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
* @param {Object} config
* @return {String}
*/
function expand(abbr, config) {
config = expand_es__extends({}, config);
if (typeof abbr === 'string') {
abbr = expand_es_parse(abbr, config);
}
return markup_formatters_es(abbr, config.profile, config.syntax, config);
}
/**
* Parses given Emmet abbreviation into a final abbreviation tree with all
* required transformations applied
* @param {String} Abbreviation to parse
* @param {Object} config
* @return {Node}
*/
function expand_es_parse(abbr, config) {
return abbreviation_es(abbr).use(html_snippets_resolver_es, config.snippets).use(variable_resolver_es, config.variables).use(html_transform_es, config.text, config.options);
}
/**
* Expands given abbreviation into code
* @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
* @param {Object} config
* @return {String}
*/
function expand$1(abbr, config) {
config = config || {};
if (typeof abbr === 'string') {
abbr = parse$1(abbr, config);
}
return stylesheet_formatters_es(abbr, config.profile, config.syntax, config);
}
/**
* Parses given Emmet abbreviation into a final abbreviation tree with all
* required transformations applied
* @param {String|Node} abbr Abbreviation to parse or already parsed abbreviation
* @param {Object} config
* @return {Node}
*/
function parse$1(abbr, config) {
if (typeof abbr === 'string') {
abbr = css_abbreviation_es(abbr);
}
return abbr.use(css_snippets_resolver_es, config.snippets, config.options);
}
const reLorem = /^lorem([a-z]*)(\d*)$/i;
/**
* Constructs a snippets registry, filled with snippets, for given options
* @param {String} syntax Abbreviation syntax
* @param {Object|Object[]} snippets Additional snippets
* @return {SnippetsRegistry}
*/
function snippetsRegistryFactory(type, syntax, snippets) {
const registrySnippets = [];
if (type === 'markup') {
registrySnippets.push(snippets_es.html);
} else if (type === 'stylesheet') {
registrySnippets.push(snippets_es.css);
}
if (syntax in snippets_es && registrySnippets.indexOf(snippets_es[syntax]) === -1) {
registrySnippets.push(snippets_es[syntax]);
}
if (Array.isArray(snippets)) {
snippets.forEach(item => {
// if array item is a string, treat it as a reference to globally
// defined snippets
registrySnippets.push(typeof item === 'string' ? snippets_es[item] : item);
});
} else if (typeof snippets === 'object') {
registrySnippets.push(snippets);
}
const registry = new snippets_registry_es(registrySnippets.filter(Boolean));
// for non-stylesheet syntaxes add Lorem Ipsum generator
if (type !== 'stylesheet') {
registry.get(0).set(reLorem, loremGenerator);
}
return registry;
}
function loremGenerator(node) {
const options = {};
const m = node.name.match(reLorem);
if (m[1]) {
options.lang = m[1];
}
if (m[2]) {
options.wordCount = +m[2];
}
return lorem_es(node, options);
}
/**
* Default variables used in snippets to insert common values into predefined snippets
* @type {Object}
*/
const defaultVariables = {
lang: 'en',
locale: 'en-US',
charset: 'UTF-8'
};
/**
* A list of syntaxes that should use Emmet CSS abbreviations:
* a variations of default abbreviation that holds values right in abbreviation name
* @type {Array}
*/
const stylesheetSyntaxes = ['css', 'sass', 'scss', 'less', 'stylus', 'sss'];
const expand_es_defaultOptions = {
/**
* Type of abbreviation to parse: 'markup' or 'stylesheet'.
* Can be auto-detected from `syntax` property. Default is 'markup'
*/
type: null,
/**
* Abbreviation output syntax
* @type {String}
*/
syntax: 'html',
/**
* Field/tabstop generator for editor. Most editors support TextMate-style
* fields: ${0} or ${1:item}. So for TextMate-style fields this function
* will look like this:
* @example
* (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`
*
* @param {Number} index Placeholder index. Fields with the same indices
* should be linked
* @param {String} [placeholder] Field placeholder
* @return {String}
*/
field: (index, placeholder) => placeholder || '',
/**
* Insert given text string(s) into expanded abbreviation
* If array of strings is given, the implicitly repeated element (e.g. `li*`)
* will be repeated by the amount of items in array
* @type {String|String[]}
*/
text: null,
/**
* Either predefined output profile or options for output profile. Used for
* abbreviation output
* @type {Profile|Object}
*/
profile: null,
/**
* Custom variables for variable resolver
* @see @emmetio/variable-resolver
* @type {Object}
*/
variables: {},
/**
* Custom predefined snippets for abbreviation. The expanded abbreviation
* will try to match given snippets that may contain custom elements,
* predefined attributes etc.
* May also contain array of items: either snippets (Object) or references
* to default syntax snippets (String; the key in default snippets hash)
* @see @emmetio/snippets
* @type {Object|SnippetsRegistry}
*/
snippets: {},
/**
* Hash of additional transformations that should be applied to expanded
* abbreviation, like BEM or JSX. Since these transformations introduce
* side-effect, they are disabled by default and should be enabled by
* providing a transform name as a key and transform options as value:
* @example
* {
* bem: {element: '--'},
* jsx: true // no options, just enable transform
* }
* @see @emmetio/html-transform/lib/addons
* @type {Object}
*/
options: null,
/**
* Additional options for syntax formatter
* @see @emmetio/markup-formatters
* @type {Object}
*/
format: null
};
/**
* Expands given abbreviation into string, formatted according to provided
* syntax and options
* @param {String|Node} abbr Abbreviation string or parsed abbreviation tree
* @param {String|Object} [config] Parsing and formatting options (object) or
* abbreviation syntax (string)
* @return {String}
*/
function expand$2(abbr, config) {
config = createOptions(config);
return getType(config.type, config.syntax) === 'stylesheet' ? expand$1(abbr, config) : expand(abbr, config);
}
/**
* Parses given abbreviation into AST tree. This tree can be later formatted to
* string with `expand` function
* @param {String} abbr Abbreviation to parse
* @param {String|Object} [options] Parsing and formatting options (object) or
* abbreviation syntax (string)
* @return {Node}
*/
function parse$2(abbr, options) {
options = createOptions(options);
return getType(options.type, options.syntax) === 'stylesheet' ? parse$1(abbr, options) : expand_es_parse(abbr, options);
}
/**
* Creates snippets registry for given syntax and additional `snippets`
* @param {String} type Abbreviation type, 'markup' or 'stylesheet'
* @param {String} syntax Snippets syntax, used for retrieving predefined snippets
* @param {SnippetsRegistry|Object|Object[]} [snippets] Additional snippets
* @return {SnippetsRegistry}
*/
function createSnippetsRegistry(type, syntax, snippets) {
// Backward-compatibility with <0.6
if (type && type !== 'markup' && type !== 'stylesheet') {
snippets = syntax;
syntax = type;
type = 'markup';
}
return snippets instanceof snippets_registry_es ? snippets : snippetsRegistryFactory(type, syntax, snippets);
}
function createOptions(options) {
if (typeof options === 'string') {
options = { syntax: options };
}
options = expand_es__extends({}, expand_es_defaultOptions, options);
if (options.type == null && options.syntax) {
options.type = isStylesheet(options.syntax) ? 'stylesheet' : 'markup';
}
options.format = expand_es__extends({ field: options.field }, options.format);
options.profile = createProfile(options);
options.variables = expand_es__extends({}, defaultVariables, options.variables);
options.snippets = createSnippetsRegistry(options.type, options.syntax, options.snippets);
return options;
}
/**
* Check if given syntax belongs to stylesheet markup.
* Emmet uses different abbreviation flavours: one is a default markup syntax,
* used for HTML, Slim, Pug etc, the other one is used for stylesheets and
* allows embedded values in abbreviation name
* @param {String} syntax
* @return {Boolean}
*/
function isStylesheet(syntax) {
return stylesheetSyntaxes.indexOf(syntax) !== -1;
}
/**
* Creates output profile from given options
* @param {Object} options
* @return {Profile}
*/
function createProfile(options) {
return options.profile instanceof output_profile_es ? options.profile : new output_profile_es(options.profile);
}
/**
* Returns type of abbreviation expander: either 'markup' or 'stylesheet'
* @param {String} type
* @param {String} [syntax]
*/
function getType(type, syntax) {
if (type) {
return type === 'stylesheet' ? 'stylesheet' : 'markup';
}
return isStylesheet(syntax) ? 'stylesheet' : 'markup';
}
//# sourceMappingURL=expand.es.js.map
// CONCATENATED MODULE: ../node_modules/@emmetio/css-snippets-resolver/dist/css-snippets-resolver.es.js
var dist_css_snippets_resolver_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
const dist_css_snippets_resolver_es_DASH = 45; // -
/**
* Calculates fuzzy match score of how close `abbr` matches given `string`.
* @param {String} abbr Abbreviation to score
* @param {String} string String to match
* @param {Number} [fuzziness] Fuzzy factor
* @return {Number} Match score
*/
function css_snippets_resolver_es_stringScore(abbr, string) {
abbr = abbr.toLowerCase();
string = string.toLowerCase();
if (abbr === string) {
return 1;
}
// a string MUST start with the same character as abbreviation
if (!string || abbr.charCodeAt(0) !== string.charCodeAt(0)) {
return 0;
}
const abbrLength = abbr.length;
const stringLength = string.length;
let i = 1,
j = 1,
score = stringLength;
let ch1, ch2, found, acronym;
while (i < abbrLength) {
ch1 = abbr.charCodeAt(i);
found = false;
acronym = false;
while (j < stringLength) {
ch2 = string.charCodeAt(j);
if (ch1 === ch2) {
found = true;
score += (stringLength - j) * (acronym ? 2 : 1);
break;
}
// add acronym bonus for exactly next match after unmatched `-`
acronym = ch2 === dist_css_snippets_resolver_es_DASH;
j++;
}
if (!found) {
break;
}
i++;
}
return score && score * (i / abbrLength) / css_snippets_resolver_es_sum(stringLength);
}
/**
* Calculates sum of first `n` natural numbers, e.g. 1+2+3+...n
* @param {Number} n
* @return {Number}
*/
function css_snippets_resolver_es_sum(n) {
return n * (n + 1) / 2;
}
const css_snippets_resolver_es_reProperty = /^([a-z\-]+)(?:\s*:\s*([^\n\r]+))?$/;
const dist_css_snippets_resolver_es_DASH$1 = 45; // -
/**
* Creates a special structure for resolving CSS properties from plain CSS
* snippets.
* Almost all CSS snippets are aliases for real CSS properties with available
* value variants, optionally separated by `|`. Most values are keywords that
* can be fuzzy-resolved as well. Some CSS properties are shorthands for other,
* more specific properties, like `border` and `border-style`. For such cases
* keywords from more specific properties should be available in shorthands too.
* @param {Snippet[]} snippets
* @return {CSSSnippet[]}
*/
function css_snippets_resolver_es_cssSnippets(snippets) {
return css_snippets_resolver_es_nest(snippets.map(snippet => new css_snippets_resolver_es_CSSSnippet(snippet.key, snippet.value)));
}
let css_snippets_resolver_es_CSSSnippet = class CSSSnippet {
constructor(key, value) {
this.key = key;
this.value = value;
this.property = null;
// detect if given snippet is a property
const m = value && value.match(css_snippets_resolver_es_reProperty);
if (m) {
this.property = m[1];
this.value = m[2];
}
this.dependencies = [];
}
addDependency(dep) {
this.dependencies.push(dep);
}
get defaultValue() {
return this.value != null ? css_snippets_resolver_es_splitValue(this.value)[0] : null;
}
/**
* Returns list of unique keywords for current CSS snippet and its dependencies
* @return {String[]}
*/
keywords() {
const stack = [];
const keywords = new Set();
let i = 0,
item,
candidates;
if (this.property) {
// scan valid CSS-properties only
stack.push(this);
}
while (i < stack.length) {
// NB Keep items in stack instead of push/pop to avoid possible
// circular references
item = stack[i++];
if (item.value) {
candidates = css_snippets_resolver_es_splitValue(item.value).filter(dist_css_snippets_resolver_es_isKeyword);
// extract possible keywords from snippet value
for (let j = 0; j < candidates.length; j++) {
keywords.add(candidates[j].trim());
}
// add dependencies into scan stack
for (let j = 0, deps = item.dependencies; j < deps.length; j++) {
if (stack.indexOf(deps[j]) === -1) {
stack.push(deps[j]);
}
}
}
}
return Array.from(keywords);
}
};
/**
* Nests more specific CSS properties into shorthand ones, e.g.
* background-position-x -> background-position -> background
* @param {CSSSnippet[]} snippets
* @return {CSSSnippet[]}
*/
function css_snippets_resolver_es_nest(snippets) {
snippets = snippets.sort(css_snippets_resolver_es_snippetsSort);
const stack = [];
// For sorted list of CSS properties, create dependency graph where each
// shorthand property contains its more specific one, e.g.
// backgound -> background-position -> background-position-x
for (let i = 0, cur, prev; i < snippets.length; i++) {
cur = snippets[i];
if (!cur.property) {
// not a CSS property, skip it
continue;
}
// Check if current property belongs to one from parent stack.
// Since `snippets` array is sorted, items are perfectly aligned
// from shorthands to more specific variants
while (stack.length) {
prev = stack[stack.length - 1];
if (cur.property.indexOf(prev.property) === 0 && cur.property.charCodeAt(prev.property.length) === dist_css_snippets_resolver_es_DASH$1) {
prev.addDependency(cur);
stack.push(cur);
break;
}
stack.pop();
}
if (!stack.length) {
stack.push(cur);
}
}
return snippets;
}
/**
* A sorting function for array of snippets
* @param {CSSSnippet} a
* @param {CSSSnippet} b
* @return {Number}
*/
function css_snippets_resolver_es_snippetsSort(a, b) {
if (a.key === b.key) {
return 0;
}
return a.key < b.key ? -1 : 1;
}
/**
* Check if given string is a keyword candidate
* @param {String} str
* @return {Boolean}
*/
function dist_css_snippets_resolver_es_isKeyword(str) {
return (/^\s*[\w-]+/.test(str)
);
}
function css_snippets_resolver_es_splitValue(value) {
return String(value).split('|');
}
const css_snippets_resolver_es_globalKeywords = ['auto', 'inherit', 'unset'];
const css_snippets_resolver_es_unitlessProperties = ['z-index', 'line-height', 'opacity', 'font-weight', 'zoom', 'flex', 'flex-grow', 'flex-shrink'];
const dist_css_snippets_resolver_es_defaultOptions = {
intUnit: 'px',
floatUnit: 'em',
unitAliases: {
e: 'em',
p: '%',
x: 'ex',
r: 'rem'
},
fuzzySearchMinScore: 0
};
/**
* For every node in given `tree`, finds matching snippet from `registry` and
* updates node with snippet data.
*
* This resolver uses fuzzy matching for searching matched snippets and their
* keyword values.
*/
function dist_css_snippets_resolver_es_index(tree, registry, options) {
options = dist_css_snippets_resolver_es__extends({}, dist_css_snippets_resolver_es_defaultOptions, options);
options.unitAliases = dist_css_snippets_resolver_es__extends({}, dist_css_snippets_resolver_es_defaultOptions.unitAliases, options && options.unitAliases);
const snippets = css_snippets_resolver_es_convertToCSSSnippets(registry);
tree.walk(node => dist_css_snippets_resolver_es_resolveNode(node, snippets, options));
return tree;
}
function css_snippets_resolver_es_convertToCSSSnippets(registry) {
return css_snippets_resolver_es_cssSnippets(registry.all({ type: 'string' }));
}
/**
* Resolves given node: finds matched CSS snippets using fuzzy match and resolves
* keyword aliases from node value
* @param {Node} node
* @param {CSSSnippet[]} snippets
* @param {Object} options
* @return {Node}
*/
function dist_css_snippets_resolver_es_resolveNode(node, snippets, options) {
const snippet = css_snippets_resolver_es_findBestMatch(node.name, snippets, 'key', options.fuzzySearchMinScore);
if (!snippet) {
// Edge case: `!important` snippet
return node.name === '!' ? css_snippets_resolver_es_setNodeAsText(node, '!important') : node;
}
return snippet.property ? css_snippets_resolver_es_resolveAsProperty(node, snippet, options) : css_snippets_resolver_es_resolveAsSnippet(node, snippet);
}
/**
* Resolves given parsed abbreviation node as CSS property
* @param {Node} node
* @param {CSSSnippet} snippet
* @param {Object} formatOptions
* @return {Node}
*/
function css_snippets_resolver_es_resolveAsProperty(node, snippet, formatOptions) {
const abbr = node.name;
node.name = snippet.property;
if (node.value && typeof node.value === 'object') {
// resolve keyword shortcuts
const keywords = snippet.keywords();
if (!node.value.size) {
// no value defined, try to resolve unmatched part as a keyword alias
let kw = css_snippets_resolver_es_findBestMatch(css_snippets_resolver_es_getUnmatchedPart(abbr, snippet.key), keywords);
if (!kw) {
// no matching value, try to get default one
kw = snippet.defaultValue;
if (kw && kw.indexOf('${') === -1) {
// Quick and dirty test for existing field. If not, wrap
// default value in a field
kw = `\${1:${kw}}`;
}
}
if (kw) {
node.value.add(kw);
}
} else {
// replace keyword aliases in current node value
for (let i = 0, token; i < node.value.value.length; i++) {
token = node.value.value[i];
if (token === '!') {
token = `${!i ? '${1} ' : ''}!important`;
} else if (css_snippets_resolver_es_isKeyword$1(token)) {
token = css_snippets_resolver_es_findBestMatch(token.value, keywords) || css_snippets_resolver_es_findBestMatch(token.value, css_snippets_resolver_es_globalKeywords) || token;
} else if (css_snippets_resolver_es_isNumericValue(token)) {
token = css_snippets_resolver_es_resolveNumericValue(node.name, token, formatOptions);
}
node.value.value[i] = token;
}
}
}
return node;
}
/**
* Resolves given parsed abbreviation node as a snippet: a plain code chunk
* @param {Node} node
* @param {CSSSnippet} snippet
* @return {Node}
*/
function css_snippets_resolver_es_resolveAsSnippet(node, snippet) {
return css_snippets_resolver_es_setNodeAsText(node, snippet.value);
}
/**
* Sets given parsed abbreviation node as a text snippet
* @param {Node} node
* @param {String} text
* @return {Node}
*/
function css_snippets_resolver_es_setNodeAsText(node, text) {
node.name = null;
node.value = text;
return node;
}
/**
* Finds best matching item from `items` array
* @param {String} abbr Abbreviation to match
* @param {Array} items List of items for match
* @param {String} [key] If `items` is a list of objects, use `key` as object
* property to test against
* @param {Number} fuzzySearchMinScore The minimum score the best matched item should have to be a valid match.
* @return {*}
*/
function css_snippets_resolver_es_findBestMatch(abbr, items, key, fuzzySearchMinScore) {
if (!abbr) {
return null;
}
let matchedItem = null;
let maxScore = 0;
fuzzySearchMinScore = fuzzySearchMinScore || 0;
for (let i = 0, item; i < items.length; i++) {
item = items[i];
const score = css_snippets_resolver_es_stringScore(abbr, css_snippets_resolver_es_getScoringPart(item, key));
if (score === 1) {
// direct hit, no need to look further
return item;
}
if (score && score >= maxScore) {
maxScore = score;
matchedItem = item;
}
}
return maxScore >= fuzzySearchMinScore ? matchedItem : null;
}
function css_snippets_resolver_es_getScoringPart(item, key) {
const value = item && typeof item === 'object' ? item[key] : item;
const m = (value || '').match(/^[\w-@]+/);
return m ? m[0] : value;
}
/**
* Returns a part of `abbr` that wasn’t directly matched agains `string`.
* For example, if abbreviation `poas` is matched against `position`, the unmatched part will be `as`
* since `a` wasn’t found in string stream
* @param {String} abbr
* @param {String} string
* @return {String}
*/
function css_snippets_resolver_es_getUnmatchedPart(abbr, string) {
for (let i = 0, lastPos = 0; i < abbr.length; i++) {
lastPos = string.indexOf(abbr[i], lastPos);
if (lastPos === -1) {
return abbr.slice(i);
}
lastPos++;
}
return '';
}
/**
* Check if given CSS value token is a keyword
* @param {*} token
* @return {Boolean}
*/
function css_snippets_resolver_es_isKeyword$1(token) {
return css_snippets_resolver_es_tokenTypeOf(token, 'keyword');
}
/**
* Check if given CSS value token is a numeric value
* @param {*} token
* @return {Boolean}
*/
function css_snippets_resolver_es_isNumericValue(token) {
return css_snippets_resolver_es_tokenTypeOf(token, 'numeric');
}
function css_snippets_resolver_es_tokenTypeOf(token, type) {
return token && typeof token === 'object' && token.type === type;
}
/**
* Resolves numeric value for given CSS property
* @param {String} property CSS property name
* @param {NumericValue} token CSS numeric value token
* @param {Object} formatOptions Formatting options for units
* @return {NumericValue}
*/
function css_snippets_resolver_es_resolveNumericValue(property, token, formatOptions) {
if (token.unit) {
token.unit = formatOptions.unitAliases[token.unit] || token.unit;
} else if (token.value !== 0 && css_snippets_resolver_es_unitlessProperties.indexOf(property) === -1) {
// use `px` for integers, `em` for floats
// NB: num|0 is a quick alternative to Math.round(0)
token.unit = token.value === (token.value | 0) ? formatOptions.intUnit : formatOptions.floatUnit;
}
return token;
}
/* harmony default export */ var dist_css_snippets_resolver_es = (dist_css_snippets_resolver_es_index);
//# sourceMappingURL=css-snippets-resolver.es.js.map
// CONCATENATED MODULE: ../node_modules/@emmetio/html-matcher/dist/html-matcher.es.js
var html_matcher_es__extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
let html_matcher_es_Node = class Node {
constructor(stream, type, open, close) {
this.stream = stream;
this.type = type;
this.open = open;
this.close = close;
this.children = [];
this.parent = null;
}
/**
* Returns node name
* @return {String}
*/
get name() {
if (this.type === 'tag' && this.open) {
return this.open && this.open.name && this.open.name.value;
}
return '#' + this.type;
}
/**
* Returns attributes of current node
* @return {Array}
*/
get attributes() {
return this.open && this.open.attributes;
}
/**
* Returns node’s start position in stream
* @return {*}
*/
get start() {
return this.open && this.open.start;
}
/**
* Returns node’s start position in stream
* @return {*}
*/
get end() {
return this.close ? this.close.end : this.open && this.open.end;
}
get firstChild() {
return this.children[0];
}
get nextSibling() {
const ix = this.getIndex();
return ix !== -1 ? this.parent.children[ix + 1] : null;
}
get previousSibling() {
const ix = this.getIndex();
return ix !== -1 ? this.parent.children[ix - 1] : null;
}
/**
* Returns current element’s index in parent list of child nodes
* @return {Number}
*/
getIndex() {
return this.parent ? this.parent.children.indexOf(this) : -1;
}
/**
* Adds given node as a child
* @param {Node} node
* @return {Node} Current node
*/
addChild(node) {
this.removeChild(node);
this.children.push(node);
node.parent = this;
return this;
}
/**
* Removes given node from current node’s child list
* @param {Node} node
* @return {Node} Current node
*/
removeChild(node) {
const ix = this.children.indexOf(node);
if (ix !== -1) {
this.children.splice(ix, 1);
node.parent = null;
}
return this;
}
};
/**
* A token factory method
* @param {StreamReader} stream
* @param {Point|Function} start Tokens’ start location or stream consumer
* @param {Point} [end] Tokens’ end location
* @return {Token}
*/
var html_matcher_es_token = function (stream, start, end) {
return typeof start === 'function' ? eatToken(stream, start) : new Token(stream, start, end);
};
/**
* Consumes characters from given stream that matches `fn` call and returns it
* as token, if consumed
* @param {StreamReader} stream
* @param {Function} test
* @return {Token}
*/
function eatToken(stream, test) {
const start = stream.pos;
if (stream.eatWhile(test)) {
return new Token(stream, start, stream.pos);
}
stream.pos = start;
}
/**
* A structure describing text fragment in content stream
*/
let Token = class Token {
/**
* @param {ContentStreamReader} stream
* @param {Point} start Tokens’ start location in content stream
* @param {Point} end Tokens’ end location in content stream
*/
constructor(stream, start, end) {
this.stream = stream;
this.start = start != null ? start : stream.start;
this.end = end != null ? end : stream.pos;
this._value = null;
}
/**
* Returns token textual value
* NB implemented as getter to reduce unnecessary memory allocations for
* strings that not required
* @return {String}
*/
get value() {
if (this._value === null) {
const start = this.stream.start;
const end = this.stream.pos;
this.stream.start = this.start;
this.stream.pos = this.end;
this._value = this.stream.current();
this.stream.start = start;
this.stream.pos = end;
}
return this._value;
}
toString() {
return this.value;
}
valueOf() {
return `${this.value} [${this.start}; ${this.end}]`;
}
};
const LANGLE = 60;
const RANGLE = 62; // < and >
const LSQUARE = 91;
const RSQUARE = 93; // [ and ]
const LROUND = 40;
const RROUND = 41; // ( and )
const LCURLY = 123;
const RCURLY = 125; // { and }
const html_matcher_es_opt = { throws: true };
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @param {StreamReader} stream
* @return {Token} A token with consumed paired character
*/
var eatPaired = function (stream) {
const start = stream.pos;
const consumed = eatPair(stream, LANGLE, RANGLE, html_matcher_es_opt) || eatPair(stream, LSQUARE, RSQUARE, html_matcher_es_opt) || eatPair(stream, LROUND, RROUND, html_matcher_es_opt) || eatPair(stream, LCURLY, RCURLY, html_matcher_es_opt);
if (consumed) {
return html_matcher_es_token(stream, start);
}
};
const SLASH$1 = 47; // /
const html_matcher_es_EQUALS = 61; // =
const RIGHT_ANGLE$1 = 62; // >
/**
* Consumes attributes from given stream
* @param {StreamReader} stream
* @return {Array} Array of consumed attributes
*/
var eatAttributes = function (stream) {
const result = [];
let name, value, attr;
while (!stream.eof()) {
stream.eatWhile(isSpace);
attr = { start: stream.pos };
// A name could be a regular name or expression:
// React-style –