File indexing completed on 2024-12-29 03:43:59
0001 /* 0002 Underscore.js 1.3.3 0003 0004 SPDX-FileCopyrightText: 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 0005 0006 SPDX-License-Identifier: MIT 0007 0008 Portions of Underscore are inspired or borrowed from Prototype, 0009 Oliver Steele's Functional, and John Resig's Micro-Templating. 0010 For all details and documentation: 0011 http://documentcloud.github.com/underscore 0012 */ 0013 0014 var _ = (function() { 0015 0016 // Baseline setup 0017 // -------------- 0018 0019 // Establish the root object, `window` in the browser, or `global` on the server. 0020 var root = this; 0021 0022 // Save the previous value of the `_` variable. 0023 var previousUnderscore = root._; 0024 0025 // Establish the object that gets returned to break out of a loop iteration. 0026 var breaker = {}; 0027 0028 // Save bytes in the minified (but not gzipped) version: 0029 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 0030 0031 // Create quick reference variables for speed access to core prototypes. 0032 var slice = ArrayProto.slice, 0033 unshift = ArrayProto.unshift, 0034 toString = ObjProto.toString, 0035 hasOwnProperty = ObjProto.hasOwnProperty; 0036 0037 // All **ECMAScript 5** native function implementations that we hope to use 0038 // are declared here. 0039 var 0040 nativeForEach = ArrayProto.forEach, 0041 nativeMap = ArrayProto.map, 0042 nativeReduce = ArrayProto.reduce, 0043 nativeReduceRight = ArrayProto.reduceRight, 0044 nativeFilter = ArrayProto.filter, 0045 nativeEvery = ArrayProto.every, 0046 nativeSome = ArrayProto.some, 0047 nativeIndexOf = ArrayProto.indexOf, 0048 nativeLastIndexOf = ArrayProto.lastIndexOf, 0049 nativeIsArray = Array.isArray, 0050 nativeKeys = Object.keys, 0051 nativeBind = FuncProto.bind; 0052 0053 // Create a safe reference to the Underscore object for use below. 0054 var _ = function(obj) { return new wrapper(obj); }; 0055 0056 // Export the Underscore object for **Node.js**, with 0057 // backwards-compatibility for the old `require()` API. If we're in 0058 // the browser, add `_` as a global object via a string identifier, 0059 // for Closure Compiler "advanced" mode. 0060 if (typeof exports !== 'undefined') { 0061 if (typeof module !== 'undefined' && module.exports) { 0062 exports = module.exports = _; 0063 } 0064 exports._ = _; 0065 } else { 0066 root['_'] = _; 0067 } 0068 0069 // Current version. 0070 _.VERSION = '1.3.3'; 0071 0072 // Collection Functions 0073 // -------------------- 0074 0075 // The cornerstone, an `each` implementation, aka `forEach`. 0076 // Handles objects with the built-in `forEach`, arrays, and raw objects. 0077 // Delegates to **ECMAScript 5**'s native `forEach` if available. 0078 var each = _.each = _.forEach = function(obj, iterator, context) { 0079 if (obj == null) return; 0080 if (nativeForEach && obj.forEach === nativeForEach) { 0081 obj.forEach(iterator, context); 0082 } else if (obj.length === +obj.length) { 0083 for (var i = 0, l = obj.length; i < l; i++) { 0084 if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; 0085 } 0086 } else { 0087 for (var key in obj) { 0088 if (_.has(obj, key)) { 0089 if (iterator.call(context, obj[key], key, obj) === breaker) return; 0090 } 0091 } 0092 } 0093 }; 0094 0095 // Return the results of applying the iterator to each element. 0096 // Delegates to **ECMAScript 5**'s native `map` if available. 0097 _.map = _.collect = function(obj, iterator, context) { 0098 var results = []; 0099 if (obj == null) return results; 0100 if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 0101 each(obj, function(value, index, list) { 0102 results[results.length] = iterator.call(context, value, index, list); 0103 }); 0104 if (obj.length === +obj.length) results.length = obj.length; 0105 return results; 0106 }; 0107 0108 // **Reduce** builds up a single result from a list of values, aka `inject`, 0109 // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 0110 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 0111 var initial = arguments.length > 2; 0112 if (obj == null) obj = []; 0113 if (nativeReduce && obj.reduce === nativeReduce) { 0114 if (context) iterator = _.bind(iterator, context); 0115 return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 0116 } 0117 each(obj, function(value, index, list) { 0118 if (!initial) { 0119 memo = value; 0120 initial = true; 0121 } else { 0122 memo = iterator.call(context, memo, value, index, list); 0123 } 0124 }); 0125 if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 0126 return memo; 0127 }; 0128 0129 // The right-associative version of reduce, also known as `foldr`. 0130 // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 0131 _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 0132 var initial = arguments.length > 2; 0133 if (obj == null) obj = []; 0134 if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 0135 if (context) iterator = _.bind(iterator, context); 0136 return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 0137 } 0138 var reversed = _.toArray(obj).reverse(); 0139 if (context && !initial) iterator = _.bind(iterator, context); 0140 return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); 0141 }; 0142 0143 // Return the first value which passes a truth test. Aliased as `detect`. 0144 _.find = _.detect = function(obj, iterator, context) { 0145 var result; 0146 any(obj, function(value, index, list) { 0147 if (iterator.call(context, value, index, list)) { 0148 result = value; 0149 return true; 0150 } 0151 }); 0152 return result; 0153 }; 0154 0155 // Return all the elements that pass a truth test. 0156 // Delegates to **ECMAScript 5**'s native `filter` if available. 0157 // Aliased as `select`. 0158 _.filter = _.select = function(obj, iterator, context) { 0159 var results = []; 0160 if (obj == null) return results; 0161 if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 0162 each(obj, function(value, index, list) { 0163 if (iterator.call(context, value, index, list)) results[results.length] = value; 0164 }); 0165 return results; 0166 }; 0167 0168 // Return all the elements for which a truth test fails. 0169 _.reject = function(obj, iterator, context) { 0170 var results = []; 0171 if (obj == null) return results; 0172 each(obj, function(value, index, list) { 0173 if (!iterator.call(context, value, index, list)) results[results.length] = value; 0174 }); 0175 return results; 0176 }; 0177 0178 // Determine whether all of the elements match a truth test. 0179 // Delegates to **ECMAScript 5**'s native `every` if available. 0180 // Aliased as `all`. 0181 _.every = _.all = function(obj, iterator, context) { 0182 var result = true; 0183 if (obj == null) return result; 0184 if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 0185 each(obj, function(value, index, list) { 0186 if (!(result = result && iterator.call(context, value, index, list))) return breaker; 0187 }); 0188 return !!result; 0189 }; 0190 0191 // Determine if at least one element in the object matches a truth test. 0192 // Delegates to **ECMAScript 5**'s native `some` if available. 0193 // Aliased as `any`. 0194 var any = _.some = _.any = function(obj, iterator, context) { 0195 iterator || (iterator = _.identity); 0196 var result = false; 0197 if (obj == null) return result; 0198 if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 0199 each(obj, function(value, index, list) { 0200 if (result || (result = iterator.call(context, value, index, list))) return breaker; 0201 }); 0202 return !!result; 0203 }; 0204 0205 // Determine if a given value is included in the array or object using `===`. 0206 // Aliased as `contains`. 0207 _.include = _.contains = function(obj, target) { 0208 var found = false; 0209 if (obj == null) return found; 0210 if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 0211 found = any(obj, function(value) { 0212 return value === target; 0213 }); 0214 return found; 0215 }; 0216 0217 // Invoke a method (with arguments) on every item in a collection. 0218 _.invoke = function(obj, method) { 0219 var args = slice.call(arguments, 2); 0220 return _.map(obj, function(value) { 0221 return (_.isFunction(method) ? method || value : value[method]).apply(value, args); 0222 }); 0223 }; 0224 0225 // Convenience version of a common use case of `map`: fetching a property. 0226 _.pluck = function(obj, key) { 0227 return _.map(obj, function(value){ return value[key]; }); 0228 }; 0229 0230 // Return the maximum element or (element-based computation). 0231 _.max = function(obj, iterator, context) { 0232 if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj); 0233 if (!iterator && _.isEmpty(obj)) return -Infinity; 0234 var result = {computed : -Infinity}; 0235 each(obj, function(value, index, list) { 0236 var computed = iterator ? iterator.call(context, value, index, list) : value; 0237 computed >= result.computed && (result = {value : value, computed : computed}); 0238 }); 0239 return result.value; 0240 }; 0241 0242 // Return the minimum element (or element-based computation). 0243 _.min = function(obj, iterator, context) { 0244 if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj); 0245 if (!iterator && _.isEmpty(obj)) return Infinity; 0246 var result = {computed : Infinity}; 0247 each(obj, function(value, index, list) { 0248 var computed = iterator ? iterator.call(context, value, index, list) : value; 0249 computed < result.computed && (result = {value : value, computed : computed}); 0250 }); 0251 return result.value; 0252 }; 0253 0254 // Shuffle an array. 0255 _.shuffle = function(obj) { 0256 var shuffled = [], rand; 0257 each(obj, function(value, index, list) { 0258 rand = Math.floor(Math.random() * (index + 1)); 0259 shuffled[index] = shuffled[rand]; 0260 shuffled[rand] = value; 0261 }); 0262 return shuffled; 0263 }; 0264 0265 // Sort the object's values by a criterion produced by an iterator. 0266 _.sortBy = function(obj, val, context) { 0267 var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; 0268 return _.pluck(_.map(obj, function(value, index, list) { 0269 return { 0270 value : value, 0271 criteria : iterator.call(context, value, index, list) 0272 }; 0273 }).sort(function(left, right) { 0274 var a = left.criteria, b = right.criteria; 0275 if (a === void 0) return 1; 0276 if (b === void 0) return -1; 0277 return a < b ? -1 : a > b ? 1 : 0; 0278 }), 'value'); 0279 }; 0280 0281 // Groups the object's values by a criterion. Pass either a string attribute 0282 // to group by, or a function that returns the criterion. 0283 _.groupBy = function(obj, val) { 0284 var result = {}; 0285 var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; 0286 each(obj, function(value, index) { 0287 var key = iterator(value, index); 0288 (result[key] || (result[key] = [])).push(value); 0289 }); 0290 return result; 0291 }; 0292 0293 // Use a comparator function to figure out at what index an object should 0294 // be inserted so as to maintain order. Uses binary search. 0295 _.sortedIndex = function(array, obj, iterator) { 0296 iterator || (iterator = _.identity); 0297 var low = 0, high = array.length; 0298 while (low < high) { 0299 var mid = (low + high) >> 1; 0300 iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 0301 } 0302 return low; 0303 }; 0304 0305 // Safely convert anything iterable into a real, live array. 0306 _.toArray = function(obj) { 0307 if (!obj) return []; 0308 if (_.isArray(obj)) return slice.call(obj); 0309 if (_.isArguments(obj)) return slice.call(obj); 0310 if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray(); 0311 return _.values(obj); 0312 }; 0313 0314 // Return the number of elements in an object. 0315 _.size = function(obj) { 0316 return _.isArray(obj) ? obj.length : _.keys(obj).length; 0317 }; 0318 0319 // Array Functions 0320 // --------------- 0321 0322 // Get the first element of an array. Passing **n** will return the first N 0323 // values in the array. Aliased as `head` and `take`. The **guard** check 0324 // allows it to work with `_.map`. 0325 _.first = _.head = _.take = function(array, n, guard) { 0326 return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 0327 }; 0328 0329 // Returns everything but the last entry of the array. Especcialy useful on 0330 // the arguments object. Passing **n** will return all the values in 0331 // the array, excluding the last N. The **guard** check allows it to work with 0332 // `_.map`. 0333 _.initial = function(array, n, guard) { 0334 return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 0335 }; 0336 0337 // Get the last element of an array. Passing **n** will return the last N 0338 // values in the array. The **guard** check allows it to work with `_.map`. 0339 _.last = function(array, n, guard) { 0340 if ((n != null) && !guard) { 0341 return slice.call(array, Math.max(array.length - n, 0)); 0342 } else { 0343 return array[array.length - 1]; 0344 } 0345 }; 0346 0347 // Returns everything but the first entry of the array. Aliased as `tail`. 0348 // Especially useful on the arguments object. Passing an **index** will return 0349 // the rest of the values in the array from that index onward. The **guard** 0350 // check allows it to work with `_.map`. 0351 _.rest = _.tail = function(array, index, guard) { 0352 return slice.call(array, (index == null) || guard ? 1 : index); 0353 }; 0354 0355 // Trim out all falsy values from an array. 0356 _.compact = function(array) { 0357 return _.filter(array, function(value){ return !!value; }); 0358 }; 0359 0360 // Return a completely flattened version of an array. 0361 _.flatten = function(array, shallow) { 0362 return _.reduce(array, function(memo, value) { 0363 if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); 0364 memo[memo.length] = value; 0365 return memo; 0366 }, []); 0367 }; 0368 0369 // Return a version of the array that does not contain the specified value(s). 0370 _.without = function(array) { 0371 return _.difference(array, slice.call(arguments, 1)); 0372 }; 0373 0374 // Produce a duplicate-free version of the array. If the array has already 0375 // been sorted, you have the option of using a faster algorithm. 0376 // Aliased as `unique`. 0377 _.uniq = _.unique = function(array, isSorted, iterator) { 0378 var initial = iterator ? _.map(array, iterator) : array; 0379 var results = []; 0380 // The `isSorted` flag is irrelevant if the array only contains two elements. 0381 if (array.length < 3) isSorted = true; 0382 _.reduce(initial, function (memo, value, index) { 0383 if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { 0384 memo.push(value); 0385 results.push(array[index]); 0386 } 0387 return memo; 0388 }, []); 0389 return results; 0390 }; 0391 0392 // Produce an array that contains the union: each distinct element from all of 0393 // the passed-in arrays. 0394 _.union = function() { 0395 return _.uniq(_.flatten(arguments, true)); 0396 }; 0397 0398 // Produce an array that contains every item shared between all the 0399 // passed-in arrays. (Aliased as "intersect" for back-compat.) 0400 _.intersection = _.intersect = function(array) { 0401 var rest = slice.call(arguments, 1); 0402 return _.filter(_.uniq(array), function(item) { 0403 return _.every(rest, function(other) { 0404 return _.indexOf(other, item) >= 0; 0405 }); 0406 }); 0407 }; 0408 0409 // Take the difference between one array and a number of other arrays. 0410 // Only the elements present in just the first array will remain. 0411 _.difference = function(array) { 0412 var rest = _.flatten(slice.call(arguments, 1), true); 0413 return _.filter(array, function(value){ return !_.include(rest, value); }); 0414 }; 0415 0416 // Zip together multiple lists into a single array -- elements that share 0417 // an index go together. 0418 _.zip = function() { 0419 var args = slice.call(arguments); 0420 var length = _.max(_.pluck(args, 'length')); 0421 var results = new Array(length); 0422 for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 0423 return results; 0424 }; 0425 0426 // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 0427 // we need this function. Return the position of the first occurrence of an 0428 // item in an array, or -1 if the item is not included in the array. 0429 // Delegates to **ECMAScript 5**'s native `indexOf` if available. 0430 // If the array is large and already in sort order, pass `true` 0431 // for **isSorted** to use binary search. 0432 _.indexOf = function(array, item, isSorted) { 0433 if (array == null) return -1; 0434 var i, l; 0435 if (isSorted) { 0436 i = _.sortedIndex(array, item); 0437 return array[i] === item ? i : -1; 0438 } 0439 if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 0440 for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; 0441 return -1; 0442 }; 0443 0444 // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 0445 _.lastIndexOf = function(array, item) { 0446 if (array == null) return -1; 0447 if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 0448 var i = array.length; 0449 while (i--) if (i in array && array[i] === item) return i; 0450 return -1; 0451 }; 0452 0453 // Generate an integer Array containing an arithmetic progression. A port of 0454 // the native Python `range()` function. See 0455 // [the Python documentation](http://docs.python.org/library/functions.html#range). 0456 _.range = function(start, stop, step) { 0457 if (arguments.length <= 1) { 0458 stop = start || 0; 0459 start = 0; 0460 } 0461 step = arguments[2] || 1; 0462 0463 var len = Math.max(Math.ceil((stop - start) / step), 0); 0464 var idx = 0; 0465 var range = new Array(len); 0466 0467 while(idx < len) { 0468 range[idx++] = start; 0469 start += step; 0470 } 0471 0472 return range; 0473 }; 0474 0475 // Function (ahem) Functions 0476 // ------------------ 0477 0478 // Reusable constructor function for prototype setting. 0479 var ctor = function(){}; 0480 0481 // Create a function bound to a given object (assigning `this`, and arguments, 0482 // optionally). Binding with arguments is also known as `curry`. 0483 // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 0484 // We check for `func.bind` first, to fail fast when `func` is undefined. 0485 _.bind = function bind(func, context) { 0486 var bound, args; 0487 if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 0488 if (!_.isFunction(func)) throw new TypeError; 0489 args = slice.call(arguments, 2); 0490 return bound = function() { 0491 if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 0492 ctor.prototype = func.prototype; 0493 var self = new ctor; 0494 var result = func.apply(self, args.concat(slice.call(arguments))); 0495 if (Object(result) === result) return result; 0496 return self; 0497 }; 0498 }; 0499 0500 // Bind all of an object's methods to that object. Useful for ensuring that 0501 // all callbacks defined on an object belong to it. 0502 _.bindAll = function(obj) { 0503 var funcs = slice.call(arguments, 1); 0504 if (funcs.length == 0) funcs = _.functions(obj); 0505 each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 0506 return obj; 0507 }; 0508 0509 // Memoize an expensive function by storing its results. 0510 _.memoize = function(func, hasher) { 0511 var memo = {}; 0512 hasher || (hasher = _.identity); 0513 return function() { 0514 var key = hasher.apply(this, arguments); 0515 return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 0516 }; 0517 }; 0518 0519 // Delays a function for the given number of milliseconds, and then calls 0520 // it with the arguments supplied. 0521 _.delay = function(func, wait) { 0522 var args = slice.call(arguments, 2); 0523 return setTimeout(function(){ return func.apply(null, args); }, wait); 0524 }; 0525 0526 // Defers a function, scheduling it to run after the current call stack has 0527 // cleared. 0528 _.defer = function(func) { 0529 return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 0530 }; 0531 0532 // Returns a function, that, when invoked, will only be triggered at most once 0533 // during a given window of time. 0534 _.throttle = function(func, wait) { 0535 var context, args, timeout, throttling, more, result; 0536 var whenDone = _.debounce(function(){ more = throttling = false; }, wait); 0537 return function() { 0538 context = this; args = arguments; 0539 var later = function() { 0540 timeout = null; 0541 if (more) func.apply(context, args); 0542 whenDone(); 0543 }; 0544 if (!timeout) timeout = setTimeout(later, wait); 0545 if (throttling) { 0546 more = true; 0547 } else { 0548 result = func.apply(context, args); 0549 } 0550 whenDone(); 0551 throttling = true; 0552 return result; 0553 }; 0554 }; 0555 0556 // Returns a function, that, as long as it continues to be invoked, will not 0557 // be triggered. The function will be called after it stops being called for 0558 // N milliseconds. If `immediate` is passed, trigger the function on the 0559 // leading edge, instead of the trailing. 0560 _.debounce = function(func, wait, immediate) { 0561 var timeout; 0562 return function() { 0563 var context = this, args = arguments; 0564 var later = function() { 0565 timeout = null; 0566 if (!immediate) func.apply(context, args); 0567 }; 0568 if (immediate && !timeout) func.apply(context, args); 0569 clearTimeout(timeout); 0570 timeout = setTimeout(later, wait); 0571 }; 0572 }; 0573 0574 // Returns a function that will be executed at most one time, no matter how 0575 // often you call it. Useful for lazy initialization. 0576 _.once = function(func) { 0577 var ran = false, memo; 0578 return function() { 0579 if (ran) return memo; 0580 ran = true; 0581 return memo = func.apply(this, arguments); 0582 }; 0583 }; 0584 0585 // Returns the first function passed as an argument to the second, 0586 // allowing you to adjust arguments, run code before and after, and 0587 // conditionally execute the original function. 0588 _.wrap = function(func, wrapper) { 0589 return function() { 0590 var args = [func].concat(slice.call(arguments, 0)); 0591 return wrapper.apply(this, args); 0592 }; 0593 }; 0594 0595 // Returns a function that is the composition of a list of functions, each 0596 // consuming the return value of the function that follows. 0597 _.compose = function() { 0598 var funcs = arguments; 0599 return function() { 0600 var args = arguments; 0601 for (var i = funcs.length - 1; i >= 0; i--) { 0602 args = [funcs[i].apply(this, args)]; 0603 } 0604 return args[0]; 0605 }; 0606 }; 0607 0608 // Returns a function that will only be executed after being called N times. 0609 _.after = function(times, func) { 0610 if (times <= 0) return func(); 0611 return function() { 0612 if (--times < 1) { return func.apply(this, arguments); } 0613 }; 0614 }; 0615 0616 // Object Functions 0617 // ---------------- 0618 0619 // Retrieve the names of an object's properties. 0620 // Delegates to **ECMAScript 5**'s native `Object.keys` 0621 _.keys = nativeKeys || function(obj) { 0622 if (obj !== Object(obj)) throw new TypeError('Invalid object'); 0623 var keys = []; 0624 for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; 0625 return keys; 0626 }; 0627 0628 // Retrieve the values of an object's properties. 0629 _.values = function(obj) { 0630 return _.map(obj, _.identity); 0631 }; 0632 0633 // Return a sorted list of the function names available on the object. 0634 // Aliased as `methods` 0635 _.functions = _.methods = function(obj) { 0636 var names = []; 0637 for (var key in obj) { 0638 if (_.isFunction(obj[key])) names.push(key); 0639 } 0640 return names.sort(); 0641 }; 0642 0643 // Extend a given object with all the properties in passed-in object(s). 0644 _.extend = function(obj) { 0645 each(slice.call(arguments, 1), function(source) { 0646 for (var prop in source) { 0647 obj[prop] = source[prop]; 0648 } 0649 }); 0650 return obj; 0651 }; 0652 0653 // Return a copy of the object only containing the whitelisted properties. 0654 _.pick = function(obj) { 0655 var result = {}; 0656 each(_.flatten(slice.call(arguments, 1)), function(key) { 0657 if (key in obj) result[key] = obj[key]; 0658 }); 0659 return result; 0660 }; 0661 0662 // Fill in a given object with default properties. 0663 _.defaults = function(obj) { 0664 each(slice.call(arguments, 1), function(source) { 0665 for (var prop in source) { 0666 if (obj[prop] == null) obj[prop] = source[prop]; 0667 } 0668 }); 0669 return obj; 0670 }; 0671 0672 // Create a (shallow-cloned) duplicate of an object. 0673 _.clone = function(obj) { 0674 if (!_.isObject(obj)) return obj; 0675 return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 0676 }; 0677 0678 // Invokes interceptor with the obj, and then returns obj. 0679 // The primary purpose of this method is to "tap into" a method chain, in 0680 // order to perform operations on intermediate results within the chain. 0681 _.tap = function(obj, interceptor) { 0682 interceptor(obj); 0683 return obj; 0684 }; 0685 0686 // Internal recursive comparison function. 0687 function eq(a, b, stack) { 0688 // Identical objects are equal. `0 === -0`, but they aren't identical. 0689 // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 0690 if (a === b) return a !== 0 || 1 / a == 1 / b; 0691 // A strict comparison is necessary because `null == undefined`. 0692 if (a == null || b == null) return a === b; 0693 // Unwrap any wrapped objects. 0694 if (a._chain) a = a._wrapped; 0695 if (b._chain) b = b._wrapped; 0696 // Invoke a custom `isEqual` method if one is provided. 0697 if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); 0698 if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); 0699 // Compare `[[Class]]` names. 0700 var className = toString.call(a); 0701 if (className != toString.call(b)) return false; 0702 switch (className) { 0703 // Strings, numbers, dates, and booleans are compared by value. 0704 case '[object String]': 0705 // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 0706 // equivalent to `new String("5")`. 0707 return a == String(b); 0708 case '[object Number]': 0709 // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 0710 // other numeric values. 0711 return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 0712 case '[object Date]': 0713 case '[object Boolean]': 0714 // Coerce dates and booleans to numeric primitive values. Dates are compared by their 0715 // millisecond representations. Note that invalid dates with millisecond representations 0716 // of `NaN` are not equivalent. 0717 return +a == +b; 0718 // RegExps are compared by their source patterns and flags. 0719 case '[object RegExp]': 0720 return a.source == b.source && 0721 a.global == b.global && 0722 a.multiline == b.multiline && 0723 a.ignoreCase == b.ignoreCase; 0724 } 0725 if (typeof a != 'object' || typeof b != 'object') return false; 0726 // Assume equality for cyclic structures. The algorithm for detecting cyclic 0727 // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 0728 var length = stack.length; 0729 while (length--) { 0730 // Linear search. Performance is inversely proportional to the number of 0731 // unique nested structures. 0732 if (stack[length] == a) return true; 0733 } 0734 // Add the first object to the stack of traversed objects. 0735 stack.push(a); 0736 var size = 0, result = true; 0737 // Recursively compare objects and arrays. 0738 if (className == '[object Array]') { 0739 // Compare array lengths to determine if a deep comparison is necessary. 0740 size = a.length; 0741 result = size == b.length; 0742 if (result) { 0743 // Deep compare the contents, ignoring non-numeric properties. 0744 while (size--) { 0745 // Ensure commutative equality for sparse arrays. 0746 if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; 0747 } 0748 } 0749 } else { 0750 // Objects with different constructors are not equivalent. 0751 if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; 0752 // Deep compare objects. 0753 for (var key in a) { 0754 if (_.has(a, key)) { 0755 // Count the expected number of properties. 0756 size++; 0757 // Deep compare each member. 0758 if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; 0759 } 0760 } 0761 // Ensure that both objects contain the same number of properties. 0762 if (result) { 0763 for (key in b) { 0764 if (_.has(b, key) && !(size--)) break; 0765 } 0766 result = !size; 0767 } 0768 } 0769 // Remove the first object from the stack of traversed objects. 0770 stack.pop(); 0771 return result; 0772 } 0773 0774 // Perform a deep comparison to check if two objects are equal. 0775 _.isEqual = function(a, b) { 0776 return eq(a, b, []); 0777 }; 0778 0779 // Is a given array, string, or object empty? 0780 // An "empty" object has no enumerable own-properties. 0781 _.isEmpty = function(obj) { 0782 if (obj == null) return true; 0783 if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 0784 for (var key in obj) if (_.has(obj, key)) return false; 0785 return true; 0786 }; 0787 0788 // Is a given value a DOM element? 0789 _.isElement = function(obj) { 0790 return !!(obj && obj.nodeType == 1); 0791 }; 0792 0793 // Is a given value an array? 0794 // Delegates to ECMA5's native Array.isArray 0795 _.isArray = nativeIsArray || function(obj) { 0796 return toString.call(obj) == '[object Array]'; 0797 }; 0798 0799 // Is a given variable an object? 0800 _.isObject = function(obj) { 0801 return obj === Object(obj); 0802 }; 0803 0804 // Is a given variable an arguments object? 0805 _.isArguments = function(obj) { 0806 return toString.call(obj) == '[object Arguments]'; 0807 }; 0808 if (!_.isArguments(arguments)) { 0809 _.isArguments = function(obj) { 0810 return !!(obj && _.has(obj, 'callee')); 0811 }; 0812 } 0813 0814 // Is a given value a function? 0815 _.isFunction = function(obj) { 0816 return toString.call(obj) == '[object Function]'; 0817 }; 0818 0819 // Is a given value a string? 0820 _.isString = function(obj) { 0821 return toString.call(obj) == '[object String]'; 0822 }; 0823 0824 // Is a given value a number? 0825 _.isNumber = function(obj) { 0826 return toString.call(obj) == '[object Number]'; 0827 }; 0828 0829 // Is a given object a finite number? 0830 _.isFinite = function(obj) { 0831 return _.isNumber(obj) && isFinite(obj); 0832 }; 0833 0834 // Is the given value `NaN`? 0835 _.isNaN = function(obj) { 0836 // `NaN` is the only value for which `===` is not reflexive. 0837 return obj !== obj; 0838 }; 0839 0840 // Is a given value a boolean? 0841 _.isBoolean = function(obj) { 0842 return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 0843 }; 0844 0845 // Is a given value a date? 0846 _.isDate = function(obj) { 0847 return toString.call(obj) == '[object Date]'; 0848 }; 0849 0850 // Is the given value a regular expression? 0851 _.isRegExp = function(obj) { 0852 return toString.call(obj) == '[object RegExp]'; 0853 }; 0854 0855 // Is a given value equal to null? 0856 _.isNull = function(obj) { 0857 return obj === null; 0858 }; 0859 0860 // Is a given variable undefined? 0861 _.isUndefined = function(obj) { 0862 return obj === void 0; 0863 }; 0864 0865 // Has own property? 0866 _.has = function(obj, key) { 0867 return hasOwnProperty.call(obj, key); 0868 }; 0869 0870 // Utility Functions 0871 // ----------------- 0872 0873 // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 0874 // previous owner. Returns a reference to the Underscore object. 0875 _.noConflict = function() { 0876 root._ = previousUnderscore; 0877 return this; 0878 }; 0879 0880 // Keep the identity function around for default iterators. 0881 _.identity = function(value) { 0882 return value; 0883 }; 0884 0885 // Run a function **n** times. 0886 _.times = function (n, iterator, context) { 0887 for (var i = 0; i < n; i++) iterator.call(context, i); 0888 }; 0889 0890 // Escape a string for HTML interpolation. 0891 _.escape = function(string) { 0892 return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); 0893 }; 0894 0895 // If the value of the named property is a function then invoke it; 0896 // otherwise, return it. 0897 _.result = function(object, property) { 0898 if (object == null) return null; 0899 var value = object[property]; 0900 return _.isFunction(value) ? value.call(object) : value; 0901 }; 0902 0903 // Add your own custom functions to the Underscore object, ensuring that 0904 // they're correctly added to the OOP wrapper as well. 0905 _.mixin = function(obj) { 0906 each(_.functions(obj), function(name){ 0907 addToWrapper(name, _[name] = obj[name]); 0908 }); 0909 }; 0910 0911 // Generate a unique integer id (unique within the entire client session). 0912 // Useful for temporary DOM ids. 0913 var idCounter = 0; 0914 _.uniqueId = function(prefix) { 0915 var id = idCounter++; 0916 return prefix ? prefix + id : id; 0917 }; 0918 0919 // By default, Underscore uses ERB-style template delimiters, change the 0920 // following template settings to use alternative delimiters. 0921 _.templateSettings = { 0922 evaluate : /<%([\s\S]+?)%>/g, 0923 interpolate : /<%=([\s\S]+?)%>/g, 0924 escape : /<%-([\s\S]+?)%>/g 0925 }; 0926 0927 // When customizing `templateSettings`, if you don't want to define an 0928 // interpolation, evaluation or escaping regex, we need one that is 0929 // guaranteed not to match. 0930 var noMatch = /.^/; 0931 0932 // Certain characters need to be escaped so that they can be put into a 0933 // string literal. 0934 var escapes = { 0935 '\\': '\\', 0936 "'": "'", 0937 'r': '\r', 0938 'n': '\n', 0939 't': '\t', 0940 'u2028': '\u2028', 0941 'u2029': '\u2029' 0942 }; 0943 0944 for (var p in escapes) escapes[escapes[p]] = p; 0945 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 0946 var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; 0947 0948 // Within an interpolation, evaluation, or escaping, remove HTML escaping 0949 // that had been previously added. 0950 var unescape = function(code) { 0951 return code.replace(unescaper, function(match, escape) { 0952 return escapes[escape]; 0953 }); 0954 }; 0955 0956 // JavaScript micro-templating, similar to John Resig's implementation. 0957 // Underscore templating handles arbitrary delimiters, preserves whitespace, 0958 // and correctly escapes quotes within interpolated code. 0959 _.template = function(text, data, settings) { 0960 settings = _.defaults(settings || {}, _.templateSettings); 0961 0962 // Compile the template source, taking care to escape characters that 0963 // cannot be included in a string literal and then unescape them in code 0964 // blocks. 0965 var source = "__p+='" + text 0966 .replace(escaper, function(match) { 0967 return '\\' + escapes[match]; 0968 }) 0969 .replace(settings.escape || noMatch, function(match, code) { 0970 return "'+\n_.escape(" + unescape(code) + ")+\n'"; 0971 }) 0972 .replace(settings.interpolate || noMatch, function(match, code) { 0973 return "'+\n(" + unescape(code) + ")+\n'"; 0974 }) 0975 .replace(settings.evaluate || noMatch, function(match, code) { 0976 return "';\n" + unescape(code) + "\n;__p+='"; 0977 }) + "';\n"; 0978 0979 // If a variable is not specified, place data values in local scope. 0980 if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 0981 0982 source = "var __p='';" + 0983 "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + 0984 source + "return __p;\n"; 0985 0986 var render = new Function(settings.variable || 'obj', '_', source); 0987 if (data) return render(data, _); 0988 var template = function(data) { 0989 return render.call(this, data, _); 0990 }; 0991 0992 // Provide the compiled function source as a convenience for build time 0993 // precompilation. 0994 template.source = 'function(' + (settings.variable || 'obj') + '){\n' + 0995 source + '}'; 0996 0997 return template; 0998 }; 0999 1000 // Add a "chain" function, which will delegate to the wrapper. 1001 _.chain = function(obj) { 1002 return _(obj).chain(); 1003 }; 1004 1005 // The OOP Wrapper 1006 // --------------- 1007 1008 // If Underscore is called as a function, it returns a wrapped object that 1009 // can be used OO-style. This wrapper holds altered versions of all the 1010 // underscore functions. Wrapped objects may be chained. 1011 var wrapper = function(obj) { this._wrapped = obj; }; 1012 1013 // Expose `wrapper.prototype` as `_.prototype` 1014 _.prototype = wrapper.prototype; 1015 1016 // Helper function to continue chaining intermediate results. 1017 var result = function(obj, chain) { 1018 return chain ? _(obj).chain() : obj; 1019 }; 1020 1021 // A method to easily add functions to the OOP wrapper. 1022 var addToWrapper = function(name, func) { 1023 wrapper.prototype[name] = function() { 1024 var args = slice.call(arguments); 1025 unshift.call(args, this._wrapped); 1026 return result(func.apply(_, args), this._chain); 1027 }; 1028 }; 1029 1030 // Add all of the Underscore functions to the wrapper object. 1031 _.mixin(_); 1032 1033 // Add all mutator Array functions to the wrapper. 1034 each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1035 var method = ArrayProto[name]; 1036 wrapper.prototype[name] = function() { 1037 var wrapped = this._wrapped; 1038 method.apply(wrapped, arguments); 1039 var length = wrapped.length; 1040 if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; 1041 return result(wrapped, this._chain); 1042 }; 1043 }); 1044 1045 // Add all accessor Array functions to the wrapper. 1046 each(['concat', 'join', 'slice'], function(name) { 1047 var method = ArrayProto[name]; 1048 wrapper.prototype[name] = function() { 1049 return result(method.apply(this._wrapped, arguments), this._chain); 1050 }; 1051 }); 1052 1053 // Start chaining a wrapped Underscore object. 1054 wrapper.prototype.chain = function() { 1055 this._chain = true; 1056 return this; 1057 }; 1058 1059 // Extracts the result from a wrapped and chained object. 1060 wrapper.prototype.value = function() { 1061 return this._wrapped; 1062 }; 1063 return _; 1064 }).call({}); 1065 /** 1066 * Core Emmet object, available in global scope 1067 */ 1068 var emmet = (function(global) { 1069 var defaultSyntax = 'html'; 1070 var defaultProfile = 'plain'; 1071 1072 if (typeof _ == 'undefined') { 1073 try { 1074 // avoid collisions with RequireJS loader 1075 // also, JS obfuscators tends to translate 1076 // a["name"] to a.name, which also breaks RequireJS 1077 _ = global[['require'][0]]('underscore'); // node.js 1078 } catch (e) {} 1079 } 1080 1081 if (typeof _ == 'undefined') { 1082 throw 'Cannot access to Underscore.js lib'; 1083 } 1084 1085 /** List of registered modules */ 1086 var modules = { 1087 _ : _ 1088 }; 1089 1090 /** 1091 * Shared empty constructor function to aid in prototype-chain creation. 1092 */ 1093 var ctor = function(){}; 1094 1095 /** 1096 * Helper function to correctly set up the prototype chain, for subclasses. 1097 * Similar to `goog.inherits`, but uses a hash of prototype properties and 1098 * class properties to be extended. 1099 * Took it from Backbone. 1100 * @param {Object} parent 1101 * @param {Object} protoProps 1102 * @param {Object} staticProps 1103 * @returns {Object} 1104 */ 1105 function inherits(parent, protoProps, staticProps) { 1106 var child; 1107 1108 // The constructor function for the new subclass is either defined by 1109 // you (the "constructor" property in your `extend` definition), or 1110 // defaulted by us to simply call the parent's constructor. 1111 if (protoProps && protoProps.hasOwnProperty('constructor')) { 1112 child = protoProps.constructor; 1113 } else { 1114 child = function() { 1115 parent.apply(this, arguments); 1116 }; 1117 } 1118 1119 // Inherit class (static) properties from parent. 1120 _.extend(child, parent); 1121 1122 // Set the prototype chain to inherit from `parent`, without calling 1123 // `parent`'s constructor function. 1124 ctor.prototype = parent.prototype; 1125 child.prototype = new ctor(); 1126 1127 // Add prototype properties (instance properties) to the subclass, 1128 // if supplied. 1129 if (protoProps) 1130 _.extend(child.prototype, protoProps); 1131 1132 // Add static properties to the constructor function, if supplied. 1133 if (staticProps) 1134 _.extend(child, staticProps); 1135 1136 // Correctly set child's `prototype.constructor`. 1137 child.prototype.constructor = child; 1138 1139 // Set a convenience property in case the parent's prototype is needed 1140 // later. 1141 child.__super__ = parent.prototype; 1142 1143 return child; 1144 } 1145 1146 /** 1147 * @type Function Function that loads module definition if it's not defined 1148 */ 1149 var moduleLoader = null; 1150 1151 /** 1152 * Generic Emmet module loader (actually, it doesn’t load anything, just 1153 * returns module reference). Not using `require` name to avoid conflicts 1154 * with Node.js and RequireJS 1155 */ 1156 function r(name) { 1157 if (!(name in modules) && moduleLoader) 1158 moduleLoader(name); 1159 1160 return modules[name]; 1161 } 1162 1163 return { 1164 /** 1165 * Simple, AMD-like module definition. The module will be added into 1166 * <code>emmet</code> object and will be available via 1167 * <code>emmet.require(name)</code> or <code>emmet[name]</code> 1168 * @param {String} name 1169 * @param {Function} factory 1170 * @memberOf emmet 1171 */ 1172 define: function(name, factory) { 1173 // do not let redefine existing properties 1174 if (!(name in modules)) { 1175 modules[name] = _.isFunction(factory) 1176 ? this.exec(factory) 1177 : factory; 1178 } 1179 }, 1180 1181 /** 1182 * Returns reference to Emmet module 1183 * @param {String} name Module name 1184 */ 1185 require: r, 1186 1187 /** 1188 * Helper method that just executes passed function but with all 1189 * important arguments like 'require' and '_' 1190 * @param {Function} fn 1191 * @param {Object} context Execution context 1192 */ 1193 exec: function(fn, context) { 1194 return fn.call(context || global, _.bind(r, this), _, this); 1195 }, 1196 1197 /** 1198 * The self-propagating extend function for classes. 1199 * Took it from Backbone 1200 * @param {Object} protoProps 1201 * @param {Object} classProps 1202 * @returns {Object} 1203 */ 1204 extend: function(protoProps, classProps) { 1205 var child = inherits(this, protoProps, classProps); 1206 child.extend = this.extend; 1207 // a hack required to WSH inherit `toString` method 1208 if (protoProps.hasOwnProperty('toString')) 1209 child.prototype.toString = protoProps.toString; 1210 return child; 1211 }, 1212 1213 /** 1214 * The essential function that expands Emmet abbreviation 1215 * @param {String} abbr Abbreviation to parse 1216 * @param {String} syntax Abbreviation's context syntax 1217 * @param {String} profile Output profile (or its name) 1218 * @param {Object} contextNode Contextual node where abbreviation is 1219 * written 1220 * @return {String} 1221 */ 1222 expandAbbreviation: function(abbr, syntax, profile, contextNode) { 1223 if (!abbr) return ''; 1224 1225 syntax = syntax || defaultSyntax; 1226 // profile = profile || defaultProfile; 1227 1228 var filters = r('filters'); 1229 var parser = r('abbreviationParser'); 1230 1231 profile = r('profile').get(profile, syntax); 1232 r('tabStops').resetTabstopIndex(); 1233 1234 var data = filters.extractFromAbbreviation(abbr); 1235 var outputTree = parser.parse(data[0], { 1236 syntax: syntax, 1237 contextNode: contextNode 1238 }); 1239 1240 var filtersList = filters.composeList(syntax, profile, data[1]); 1241 filters.apply(outputTree, filtersList, profile); 1242 return outputTree.valueOf(); 1243 }, 1244 1245 /** 1246 * Returns default syntax name used in abbreviation engine 1247 * @returns {String} 1248 */ 1249 defaultSyntax: function() { 1250 return defaultSyntax; 1251 }, 1252 1253 /** 1254 * Returns default profile name used in abbreviation engine 1255 * @returns {String} 1256 */ 1257 defaultProfile: function() { 1258 return defaultProfile; 1259 }, 1260 1261 /** 1262 * Log message into console if it exists 1263 */ 1264 log: function() { 1265 if (global.console && global.console.log) 1266 global.console.log.apply(global.console, arguments); 1267 }, 1268 1269 /** 1270 * Setups function that should synchronously load undefined modules 1271 * @param {Function} fn 1272 */ 1273 setModuleLoader: function(fn) { 1274 moduleLoader = fn; 1275 } 1276 }; 1277 })(this); 1278 1279 // export core for Node.JS 1280 if (typeof exports !== 'undefined') { 1281 if (typeof module !== 'undefined' && module.exports) { 1282 exports = module.exports = emmet; 1283 } 1284 exports.emmet = emmet; 1285 } 1286 1287 // export as Require.js module 1288 if (typeof define !== 'undefined') { 1289 define(emmet); 1290 }/** 1291 * Emmet abbreviation parser. 1292 * Takes string abbreviation and recursively parses it into a tree. The parsed 1293 * tree can be transformed into a string representation with 1294 * <code>toString()</code> method. Note that string representation is defined 1295 * by custom processors (called <i>filters</i>), not by abbreviation parser 1296 * itself. 1297 * 1298 * This module can be extended with custom pre-/post-processors to shape-up 1299 * final tree or its representation. Actually, many features of abbreviation 1300 * engine are defined in other modules as tree processors 1301 * 1302 * 1303 * @author Sergey Chikuyonok (serge.che@gmail.com) 1304 * @link http://chikuyonok.ru 1305 * @memberOf __abbreviationParser 1306 * @constructor 1307 * @param {Function} require 1308 * @param {Underscore} _ 1309 */ 1310 emmet.define('abbreviationParser', function(require, _) { 1311 var reValidName = /^[\w\-\$\:@\!%]+\+?$/i; 1312 var reWord = /[\w\-:\$@]/; 1313 1314 var pairs = { 1315 '[': ']', 1316 '(': ')', 1317 '{': '}' 1318 }; 1319 1320 var spliceFn = Array.prototype.splice; 1321 1322 var preprocessors = []; 1323 var postprocessors = []; 1324 var outputProcessors = []; 1325 1326 /** 1327 * @type AbbreviationNode 1328 */ 1329 function AbbreviationNode(parent) { 1330 /** @type AbbreviationNode */ 1331 this.parent = null; 1332 this.children = []; 1333 this._attributes = []; 1334 1335 /** @type String Raw abbreviation for current node */ 1336 this.abbreviation = ''; 1337 this.counter = 1; 1338 this._name = null; 1339 this._text = ''; 1340 this.repeatCount = 1; 1341 this.hasImplicitRepeat = false; 1342 1343 /** Custom data dictionary */ 1344 this._data = {}; 1345 1346 // output properties 1347 this.start = ''; 1348 this.end = ''; 1349 this.content = ''; 1350 this.padding = ''; 1351 } 1352 1353 AbbreviationNode.prototype = { 1354 /** 1355 * Adds passed node as child or creates new child 1356 * @param {AbbreviationNode} child 1357 * @param {Number} position Index in children array where child should 1358 * be inserted 1359 * @return {AbbreviationNode} 1360 */ 1361 addChild: function(child, position) { 1362 child = child || new AbbreviationNode(); 1363 child.parent = this; 1364 1365 if (_.isUndefined(position)) { 1366 this.children.push(child); 1367 } else { 1368 this.children.splice(position, 0, child); 1369 } 1370 1371 return child; 1372 }, 1373 1374 /** 1375 * Creates a deep copy of current node 1376 * @returns {AbbreviationNode} 1377 */ 1378 clone: function() { 1379 var node = new AbbreviationNode(); 1380 var attrs = ['abbreviation', 'counter', '_name', '_text', 'repeatCount', 'hasImplicitRepeat', 'start', 'end', 'content', 'padding']; 1381 _.each(attrs, function(a) { 1382 node[a] = this[a]; 1383 }, this); 1384 1385 // clone attributes 1386 node._attributes = _.map(this._attributes, function(attr) { 1387 return _.clone(attr); 1388 }); 1389 1390 node._data = _.clone(this._data); 1391 1392 // clone children 1393 node.children = _.map(this.children, function(child) { 1394 child = child.clone(); 1395 child.parent = node; 1396 return child; 1397 }); 1398 1399 return node; 1400 }, 1401 1402 /** 1403 * Removes current node from parent‘s child list 1404 * @returns {AbbreviationNode} Current node itself 1405 */ 1406 remove: function() { 1407 if (this.parent) { 1408 this.parent.children = _.without(this.parent.children, this); 1409 } 1410 1411 return this; 1412 }, 1413 1414 /** 1415 * Replaces current node in parent‘s children list with passed nodes 1416 * @param {AbbreviationNode} node Replacement node or array of nodes 1417 */ 1418 replace: function() { 1419 var parent = this.parent; 1420 var ix = _.indexOf(parent.children, this); 1421 var items = _.flatten(arguments); 1422 spliceFn.apply(parent.children, [ix, 1].concat(items)); 1423 1424 // update parent 1425 _.each(items, function(item) { 1426 item.parent = parent; 1427 }); 1428 }, 1429 1430 /** 1431 * Recursively sets <code>property</code> to <code>value</code> of current 1432 * node and its children 1433 * @param {String} name Property to update 1434 * @param {Object} value New property value 1435 */ 1436 updateProperty: function(name, value) { 1437 this[name] = value; 1438 _.each(this.children, function(child) { 1439 child.updateProperty(name, value); 1440 }); 1441 1442 return this; 1443 }, 1444 1445 /** 1446 * Finds first child node that matches truth test for passed 1447 * <code>fn</code> function 1448 * @param {Function} fn 1449 * @returns {AbbreviationNode} 1450 */ 1451 find: function(fn) { 1452 return this.findAll(fn)[0]; 1453 // if (!_.isFunction(fn)) { 1454 // var elemName = fn.toLowerCase(); 1455 // fn = function(item) {return item.name().toLowerCase() == elemName;}; 1456 // } 1457 // 1458 // var result = null; 1459 // _.find(this.children, function(child) { 1460 // if (fn(child)) { 1461 // return result = child; 1462 // } 1463 // 1464 // return result = child.find(fn); 1465 // }); 1466 // 1467 // return result; 1468 }, 1469 1470 /** 1471 * Finds all child nodes that matches truth test for passed 1472 * <code>fn</code> function 1473 * @param {Function} fn 1474 * @returns {Array} 1475 */ 1476 findAll: function(fn) { 1477 if (!_.isFunction(fn)) { 1478 var elemName = fn.toLowerCase(); 1479 fn = function(item) {return item.name().toLowerCase() == elemName;}; 1480 } 1481 1482 var result = []; 1483 _.each(this.children, function(child) { 1484 if (fn(child)) 1485 result.push(child); 1486 1487 result = result.concat(child.findAll(fn)); 1488 }); 1489 1490 return _.compact(result); 1491 }, 1492 1493 /** 1494 * Sets/gets custom data 1495 * @param {String} name 1496 * @param {Object} value 1497 * @returns {Object} 1498 */ 1499 data: function(name, value) { 1500 if (arguments.length == 2) { 1501 this._data[name] = value; 1502 1503 if (name == 'resource' && require('elements').is(value, 'snippet')) { 1504 // setting snippet as matched resource: update `content` 1505 // property with snippet value 1506 this.content = value.data; 1507 if (this._text) { 1508 this.content = require('abbreviationUtils') 1509 .insertChildContent(value.data, this._text); 1510 } 1511 } 1512 } 1513 1514 return this._data[name]; 1515 }, 1516 1517 /** 1518 * Returns name of current node 1519 * @returns {String} 1520 */ 1521 name: function() { 1522 var res = this.matchedResource(); 1523 if (require('elements').is(res, 'element')) { 1524 return res.name; 1525 } 1526 1527 return this._name; 1528 }, 1529 1530 /** 1531 * Returns list of attributes for current node 1532 * @returns {Array} 1533 */ 1534 attributeList: function() { 1535 var attrs = []; 1536 1537 var res = this.matchedResource(); 1538 if (require('elements').is(res, 'element') && _.isArray(res.attributes)) { 1539 attrs = attrs.concat(res.attributes); 1540 } 1541 1542 return optimizeAttributes(attrs.concat(this._attributes)); 1543 }, 1544 1545 /** 1546 * Returns or sets attribute value 1547 * @param {String} name Attribute name 1548 * @param {String} value New attribute value 1549 * @returns {String} 1550 */ 1551 attribute: function(name, value) { 1552 if (arguments.length == 2) { 1553 // modifying attribute 1554 var ix = _.indexOf(_.pluck(this._attributes, 'name'), name.toLowerCase()); 1555 if (~ix) { 1556 this._attributes[ix].value = value; 1557 } else { 1558 this._attributes.push({ 1559 name: name, 1560 value: value 1561 }); 1562 } 1563 } 1564 1565 return (_.find(this.attributeList(), function(attr) { 1566 return attr.name == name; 1567 }) || {}).value; 1568 }, 1569 1570 /** 1571 * Returns reference to the matched <code>element</code>, if any. 1572 * See {@link elements} module for a list of available elements 1573 * @returns {Object} 1574 */ 1575 matchedResource: function() { 1576 return this.data('resource'); 1577 }, 1578 1579 /** 1580 * Returns index of current node in parent‘s children list 1581 * @returns {Number} 1582 */ 1583 index: function() { 1584 return this.parent ? _.indexOf(this.parent.children, this) : -1; 1585 }, 1586 1587 /** 1588 * Sets how many times current element should be repeated 1589 * @private 1590 */ 1591 _setRepeat: function(count) { 1592 if (count) { 1593 this.repeatCount = parseInt(count, 10) || 1; 1594 } else { 1595 this.hasImplicitRepeat = true; 1596 } 1597 }, 1598 1599 /** 1600 * Sets abbreviation that belongs to current node 1601 * @param {String} abbr 1602 */ 1603 setAbbreviation: function(abbr) { 1604 abbr = abbr || ''; 1605 1606 var that = this; 1607 1608 // find multiplier 1609 abbr = abbr.replace(/\*(\d+)?$/, function(str, repeatCount) { 1610 that._setRepeat(repeatCount); 1611 return ''; 1612 }); 1613 1614 this.abbreviation = abbr; 1615 1616 var abbrText = extractText(abbr); 1617 if (abbrText) { 1618 abbr = abbrText.element; 1619 this.content = this._text = abbrText.text; 1620 } 1621 1622 var abbrAttrs = parseAttributes(abbr); 1623 if (abbrAttrs) { 1624 abbr = abbrAttrs.element; 1625 this._attributes = abbrAttrs.attributes; 1626 } 1627 1628 this._name = abbr; 1629 1630 // validate name 1631 if (this._name && !reValidName.test(this._name)) { 1632 throw 'Invalid abbreviation'; 1633 } 1634 }, 1635 1636 /** 1637 * Returns string representation of current node 1638 * @return {String} 1639 */ 1640 valueOf: function() { 1641 var utils = require('utils'); 1642 1643 var start = this.start; 1644 var end = this.end; 1645 var content = this.content; 1646 1647 // apply output processors 1648 var node = this; 1649 _.each(outputProcessors, function(fn) { 1650 start = fn(start, node, 'start'); 1651 content = fn(content, node, 'content'); 1652 end = fn(end, node, 'end'); 1653 }); 1654 1655 1656 var innerContent = _.map(this.children, function(child) { 1657 return child.valueOf(); 1658 }).join(''); 1659 1660 content = require('abbreviationUtils').insertChildContent(content, innerContent, { 1661 keepVariable: false 1662 }); 1663 1664 return start + utils.padString(content, this.padding) + end; 1665 }, 1666 1667 toString: function() { 1668 return this.valueOf(); 1669 }, 1670 1671 /** 1672 * Check if current node contains children with empty <code>expr</code> 1673 * property 1674 * @return {Boolean} 1675 */ 1676 hasEmptyChildren: function() { 1677 return !!_.find(this.children, function(child) { 1678 return child.isEmpty(); 1679 }); 1680 }, 1681 1682 /** 1683 * Check if current node has implied name that should be resolved 1684 * @returns {Boolean} 1685 */ 1686 hasImplicitName: function() { 1687 return !this._name && !this.isTextNode(); 1688 }, 1689 1690 /** 1691 * Indicates that current element is a grouping one, e.g. has no 1692 * representation but serves as a container for other nodes 1693 * @returns {Boolean} 1694 */ 1695 isGroup: function() { 1696 return !this.abbreviation; 1697 }, 1698 1699 /** 1700 * Indicates empty node (i.e. without abbreviation). It may be a 1701 * grouping node and should not be outputted 1702 * @return {Boolean} 1703 */ 1704 isEmpty: function() { 1705 return !this.abbreviation && !this.children.length; 1706 }, 1707 1708 /** 1709 * Indicates that current node should be repeated 1710 * @returns {Boolean} 1711 */ 1712 isRepeating: function() { 1713 return this.repeatCount > 1 || this.hasImplicitRepeat; 1714 }, 1715 1716 /** 1717 * Check if current node is a text-only node 1718 * @return {Boolean} 1719 */ 1720 isTextNode: function() { 1721 return !this.name() && !this.attributeList().length; 1722 }, 1723 1724 /** 1725 * Indicates whether this node may be used to build elements or snippets 1726 * @returns {Boolean} 1727 */ 1728 isElement: function() { 1729 return !this.isEmpty() && !this.isTextNode(); 1730 }, 1731 1732 /** 1733 * Returns latest and deepest child of current tree 1734 * @returns {AbbreviationNode} 1735 */ 1736 deepestChild: function() { 1737 if (!this.children.length) 1738 return null; 1739 1740 var deepestChild = this; 1741 while (deepestChild.children.length) { 1742 deepestChild = _.last(deepestChild.children); 1743 } 1744 1745 return deepestChild; 1746 } 1747 }; 1748 1749 /** 1750 * Returns stripped string: a string without first and last character. 1751 * Used for “unquoting” strings 1752 * @param {String} str 1753 * @returns {String} 1754 */ 1755 function stripped(str) { 1756 return str.substring(1, str.length - 1); 1757 } 1758 1759 function consumeQuotedValue(stream, quote) { 1760 var ch; 1761 while ((ch = stream.next())) { 1762 if (ch === quote) 1763 return true; 1764 1765 if (ch == '\\') 1766 continue; 1767 } 1768 1769 return false; 1770 } 1771 1772 /** 1773 * Parses abbreviation into a tree 1774 * @param {String} abbr 1775 * @returns {AbbreviationNode} 1776 */ 1777 function parseAbbreviation(abbr) { 1778 abbr = require('utils').trim(abbr); 1779 1780 var root = new AbbreviationNode(); 1781 var context = root.addChild(), ch; 1782 1783 /** @type StringStream */ 1784 var stream = require('stringStream').create(abbr); 1785 var loopProtector = 1000, multiplier; 1786 var addChild = function(child) { 1787 context.addChild(child); 1788 }; 1789 1790 var consumeAbbr = function() { 1791 stream.start = stream.pos; 1792 stream.eatWhile(function(c) { 1793 if (c == '[' || c == '{') { 1794 if (stream.skipToPair(c, pairs[c])) { 1795 stream.backUp(1); 1796 return true; 1797 } 1798 1799 throw 'Invalid abbreviation: mo matching "' + pairs[c] + '" found for character at ' + stream.pos; 1800 } 1801 1802 if (c == '+') { 1803 // let's see if this is an expando marker 1804 stream.next(); 1805 var isMarker = stream.eol() || ~'+>^*'.indexOf(stream.peek()); 1806 stream.backUp(1); 1807 return isMarker; 1808 } 1809 1810 return c != '(' && isAllowedChar(c); 1811 }); 1812 }; 1813 1814 while (!stream.eol() && --loopProtector > 0) { 1815 ch = stream.peek(); 1816 1817 switch (ch) { 1818 case '(': // abbreviation group 1819 stream.start = stream.pos; 1820 if (stream.skipToPair('(', ')')) { 1821 var inner = parseAbbreviation(stripped(stream.current())); 1822 if ((multiplier = stream.match(/^\*(\d+)?/, true))) { 1823 context._setRepeat(multiplier[1]); 1824 } 1825 1826 _.each(inner.children, addChild); 1827 } else { 1828 throw 'Invalid abbreviation: mo matching ")" found for character at ' + stream.pos; 1829 } 1830 break; 1831 1832 case '>': // child operator 1833 context = context.addChild(); 1834 stream.next(); 1835 break; 1836 1837 case '+': // sibling operator 1838 context = context.parent.addChild(); 1839 stream.next(); 1840 break; 1841 1842 case '^': // climb up operator 1843 var parent = context.parent || context; 1844 context = (parent.parent || parent).addChild(); 1845 stream.next(); 1846 break; 1847 1848 default: // consume abbreviation 1849 consumeAbbr(); 1850 context.setAbbreviation(stream.current()); 1851 stream.start = stream.pos; 1852 } 1853 } 1854 1855 if (loopProtector < 1) 1856 throw 'Endless loop detected'; 1857 1858 return root; 1859 } 1860 1861 /** 1862 * Extract attributes and their values from attribute set: 1863 * <code>[attr col=3 title="Quoted string"]</code> 1864 * @param {String} attrSet 1865 * @returns {Array} 1866 */ 1867 function extractAttributes(attrSet, attrs) { 1868 attrSet = require('utils').trim(attrSet); 1869 var result = []; 1870 1871 /** @type StringStream */ 1872 var stream = require('stringStream').create(attrSet); 1873 stream.eatSpace(); 1874 1875 while (!stream.eol()) { 1876 stream.start = stream.pos; 1877 if (stream.eatWhile(reWord)) { 1878 var attrName = stream.current(); 1879 var attrValue = ''; 1880 if (stream.peek() == '=') { 1881 stream.next(); 1882 stream.start = stream.pos; 1883 var quote = stream.peek(); 1884 1885 if (quote == '"' || quote == "'") { 1886 stream.next(); 1887 if (consumeQuotedValue(stream, quote)) { 1888 attrValue = stream.current(); 1889 // strip quotes 1890 attrValue = attrValue.substring(1, attrValue.length - 1); 1891 } else { 1892 throw 'Invalid attribute value'; 1893 } 1894 } else if (stream.eatWhile(/[^\s\]]/)) { 1895 attrValue = stream.current(); 1896 } else { 1897 throw 'Invalid attribute value'; 1898 } 1899 } 1900 1901 result.push({ 1902 name: attrName, 1903 value: attrValue 1904 }); 1905 stream.eatSpace(); 1906 } else { 1907 break; 1908 } 1909 } 1910 1911 return result; 1912 } 1913 1914 /** 1915 * Parses tag attributes extracted from abbreviation. If attributes found, 1916 * returns object with <code>element</code> and <code>attributes</code> 1917 * properties 1918 * @param {String} abbr 1919 * @returns {Object} Returns <code>null</code> if no attributes found in 1920 * abbreviation 1921 */ 1922 function parseAttributes(abbr) { 1923 /* 1924 * Example of incoming data: 1925 * #header 1926 * .some.data 1927 * .some.data#header 1928 * [attr] 1929 * #item[attr=Hello other="World"].class 1930 */ 1931 var result = []; 1932 var attrMap = {'#': 'id', '.': 'class'}; 1933 var nameEnd = null; 1934 1935 /** @type StringStream */ 1936 var stream = require('stringStream').create(abbr); 1937 while (!stream.eol()) { 1938 switch (stream.peek()) { 1939 case '#': // id 1940 case '.': // class 1941 if (nameEnd === null) 1942 nameEnd = stream.pos; 1943 1944 var attrName = attrMap[stream.peek()]; 1945 1946 stream.next(); 1947 stream.start = stream.pos; 1948 stream.eatWhile(reWord); 1949 result.push({ 1950 name: attrName, 1951 value: stream.current() 1952 }); 1953 break; 1954 case '[': //begin attribute set 1955 if (nameEnd === null) 1956 nameEnd = stream.pos; 1957 1958 stream.start = stream.pos; 1959 if (!stream.skipToPair('[', ']')) 1960 throw 'Invalid attribute set definition'; 1961 1962 result = result.concat( 1963 extractAttributes(stripped(stream.current())) 1964 ); 1965 break; 1966 default: 1967 stream.next(); 1968 } 1969 } 1970 1971 if (!result.length) 1972 return null; 1973 1974 return { 1975 element: abbr.substring(0, nameEnd), 1976 attributes: optimizeAttributes(result) 1977 }; 1978 } 1979 1980 /** 1981 * Optimize attribute set: remove duplicates and merge class attributes 1982 * @param attrs 1983 */ 1984 function optimizeAttributes(attrs) { 1985 // clone all attributes to make sure that original objects are 1986 // not modified 1987 attrs = _.map(attrs, function(attr) { 1988 return _.clone(attr); 1989 }); 1990 1991 var lookup = {}; 1992 return _.filter(attrs, function(attr) { 1993 if (!(attr.name in lookup)) { 1994 return lookup[attr.name] = attr; 1995 } 1996 1997 var la = lookup[attr.name]; 1998 1999 if (attr.name.toLowerCase() == 'class') { 2000 la.value += (la.value.length ? ' ' : '') + attr.value; 2001 } else { 2002 la.value = attr.value; 2003 } 2004 2005 return false; 2006 }); 2007 } 2008 2009 /** 2010 * Extract text data from abbreviation: if <code>a{hello}</code> abbreviation 2011 * is passed, returns object <code>{element: 'a', text: 'hello'}</code>. 2012 * If nothing found, returns <code>null</code> 2013 * @param {String} abbr 2014 * 2015 */ 2016 function extractText(abbr) { 2017 if (!~abbr.indexOf('{')) 2018 return null; 2019 2020 /** @type StringStream */ 2021 var stream = require('stringStream').create(abbr); 2022 while (!stream.eol()) { 2023 switch (stream.peek()) { 2024 case '[': 2025 case '(': 2026 stream.skipToPair(stream.peek(), pairs[stream.peek()]); break; 2027 2028 case '{': 2029 stream.start = stream.pos; 2030 stream.skipToPair('{', '}'); 2031 return { 2032 element: abbr.substring(0, stream.start), 2033 text: stripped(stream.current()) 2034 }; 2035 2036 default: 2037 stream.next(); 2038 } 2039 } 2040 } 2041 2042 /** 2043 * “Un-rolls“ contents of current node: recursively replaces all repeating 2044 * children with their repeated clones 2045 * @param {AbbreviationNode} node 2046 * @returns {AbbreviationNode} 2047 */ 2048 function unroll(node) { 2049 for (var i = node.children.length - 1, j, child, maxCount; i >= 0; i--) { 2050 child = node.children[i]; 2051 2052 if (child.isRepeating()) { 2053 maxCount = j = child.repeatCount; 2054 child.repeatCount = 1; 2055 child.updateProperty('counter', 1); 2056 child.updateProperty('maxCount', maxCount); 2057 while (--j > 0) { 2058 child.parent.addChild(child.clone(), i + 1) 2059 .updateProperty('counter', j + 1) 2060 .updateProperty('maxCount', maxCount); 2061 } 2062 } 2063 } 2064 2065 // to keep proper 'counter' property, we need to walk 2066 // on children once again 2067 _.each(node.children, unroll); 2068 2069 return node; 2070 } 2071 2072 /** 2073 * Optimizes tree node: replaces empty nodes with their children 2074 * @param {AbbreviationNode} node 2075 * @return {AbbreviationNode} 2076 */ 2077 function squash(node) { 2078 for (var i = node.children.length - 1; i >= 0; i--) { 2079 /** @type AbbreviationNode */ 2080 var n = node.children[i]; 2081 if (n.isGroup()) { 2082 n.replace(squash(n).children); 2083 } else if (n.isEmpty()) { 2084 n.remove(); 2085 } 2086 } 2087 2088 _.each(node.children, squash); 2089 2090 return node; 2091 } 2092 2093 function isAllowedChar(ch) { 2094 var charCode = ch.charCodeAt(0); 2095 var specialChars = '#.*:$-_!@|%'; 2096 2097 return (charCode > 64 && charCode < 91) // uppercase letter 2098 || (charCode > 96 && charCode < 123) // lowercase letter 2099 || (charCode > 47 && charCode < 58) // number 2100 || specialChars.indexOf(ch) != -1; // special character 2101 } 2102 2103 // XXX add counter replacer function as output processor 2104 outputProcessors.push(function(text, node) { 2105 return require('utils').replaceCounter(text, node.counter, node.maxCount); 2106 }); 2107 2108 return { 2109 /** 2110 * Parses abbreviation into tree with respect of groups, 2111 * text nodes and attributes. Each node of the tree is a single 2112 * abbreviation. Tree represents actual structure of the outputted 2113 * result 2114 * @memberOf abbreviationParser 2115 * @param {String} abbr Abbreviation to parse 2116 * @param {Object} options Additional options for parser and processors 2117 * 2118 * @return {AbbreviationNode} 2119 */ 2120 parse: function(abbr, options) { 2121 options = options || {}; 2122 2123 var tree = parseAbbreviation(abbr); 2124 2125 if (options.contextNode) { 2126 // add info about context node – 2127 // a parent XHTML node in editor inside which abbreviation is 2128 // expanded 2129 tree._name = options.contextNode.name; 2130 var attrLookup = {}; 2131 _.each(tree._attributes, function(attr) { 2132 attrLookup[attr.name] = attr; 2133 }); 2134 2135 _.each(options.contextNode.attributes, function(attr) { 2136 if (attr.name in attrLookup) { 2137 attrLookup[attr.name].value = attr.value; 2138 } else { 2139 attr = _.clone(attr); 2140 tree._attributes.push(attr); 2141 attrLookup[attr.name] = attr; 2142 } 2143 }); 2144 } 2145 2146 2147 // apply preprocessors 2148 _.each(preprocessors, function(fn) { 2149 fn(tree, options); 2150 }); 2151 2152 tree = squash(unroll(tree)); 2153 2154 // apply postprocessors 2155 _.each(postprocessors, function(fn) { 2156 fn(tree, options); 2157 }); 2158 2159 return tree; 2160 }, 2161 2162 AbbreviationNode: AbbreviationNode, 2163 2164 /** 2165 * Add new abbreviation preprocessor. <i>Preprocessor</i> is a function 2166 * that applies to a parsed abbreviation tree right after it get parsed. 2167 * The passed tree is in unoptimized state. 2168 * @param {Function} fn Preprocessor function. This function receives 2169 * two arguments: parsed abbreviation tree (<code>AbbreviationNode</code>) 2170 * and <code>options</code> hash that was passed to <code>parse</code> 2171 * method 2172 */ 2173 addPreprocessor: function(fn) { 2174 if (!_.include(preprocessors, fn)) 2175 preprocessors.push(fn); 2176 }, 2177 2178 /** 2179 * Removes registered preprocessor 2180 */ 2181 removeFilter: function(fn) { 2182 _.without(preprocessors, fn); 2183 }, 2184 2185 /** 2186 * Adds new abbreviation postprocessor. <i>Postprocessor</i> is a 2187 * functinon that applies to <i>optimized</i> parsed abbreviation tree 2188 * right before it returns from <code>parse()</code> method 2189 * @param {Function} fn Postprocessor function. This function receives 2190 * two arguments: parsed abbreviation tree (<code>AbbreviationNode</code>) 2191 * and <code>options</code> hash that was passed to <code>parse</code> 2192 * method 2193 */ 2194 addPostprocessor: function(fn) { 2195 if (!_.include(postprocessors, fn)) 2196 postprocessors.push(fn); 2197 }, 2198 2199 /** 2200 * Removes registered postprocessor function 2201 */ 2202 removePostprocessor: function(fn) { 2203 postprocessors = _.without(postprocessors, fn); 2204 }, 2205 2206 /** 2207 * Registers output postprocessor. <i>Output processor</i> is a 2208 * function that applies to output part (<code>start</code>, 2209 * <code>end</code> and <code>content</code>) when 2210 * <code>AbbreviationNode.toString()</code> method is called 2211 */ 2212 addOutputProcessor: function(fn) { 2213 if (!_.include(outputProcessors, fn)) 2214 outputProcessors.push(fn); 2215 }, 2216 2217 /** 2218 * Removes registered output processor 2219 */ 2220 removeOutputProcessor: function(fn) { 2221 outputProcessors = _.without(outputProcessors, fn); 2222 }, 2223 2224 /** 2225 * Check if passed symbol is valid symbol for abbreviation expression 2226 * @param {String} ch 2227 * @return {Boolean} 2228 */ 2229 isAllowedChar: function(ch) { 2230 ch = String(ch); // convert Java object to JS 2231 return isAllowedChar(ch) || ~'>+^[](){}'.indexOf(ch); 2232 } 2233 }; 2234 });/** 2235 * Processor function that matches parsed <code>AbbreviationNode</code> 2236 * against resources defined in <code>resource</code> module 2237 * @param {Function} require 2238 * @param {Underscore} _ 2239 */ 2240 emmet.exec(function(require, _) { 2241 /** 2242 * Finds matched resources for child nodes of passed <code>node</code> 2243 * element. A matched resource is a reference to <i>snippets.json</i> entry 2244 * that describes output of parsed node 2245 * @param {AbbreviationNode} node 2246 * @param {String} syntax 2247 */ 2248 function matchResources(node, syntax) { 2249 var resources = require('resources'); 2250 var elements = require('elements'); 2251 var parser = require('abbreviationParser'); 2252 2253 // do a shallow copy because the children list can be modified during 2254 // resource matching 2255 _.each(_.clone(node.children), /** @param {AbbreviationNode} child */ function(child) { 2256 var r = resources.getMatchedResource(child, syntax); 2257 if (_.isString(r)) { 2258 child.data('resource', elements.create('snippet', r)); 2259 } else if (elements.is(r, 'reference')) { 2260 // it’s a reference to another abbreviation: 2261 // parse it and insert instead of current child 2262 /** @type AbbreviationNode */ 2263 var subtree = parser.parse(r.data, { 2264 syntax: syntax 2265 }); 2266 2267 // if context element should be repeated, check if we need to 2268 // transfer repeated element to specific child node 2269 if (child.repeatCount > 1) { 2270 var repeatedChildren = subtree.findAll(function(node) { 2271 return node.hasImplicitRepeat; 2272 }); 2273 2274 _.each(repeatedChildren, function(node) { 2275 node.repeatCount = child.repeatCount; 2276 node.hasImplicitRepeat = false; 2277 }); 2278 } 2279 2280 // move child‘s children into the deepest child of new subtree 2281 var deepestChild = subtree.deepestChild(); 2282 if (deepestChild) { 2283 _.each(child.children, function(c) { 2284 deepestChild.addChild(c); 2285 }); 2286 } 2287 2288 // copy current attributes to children 2289 _.each(subtree.children, function(node) { 2290 _.each(child.attributeList(), function(attr) { 2291 node.attribute(attr.name, attr.value); 2292 }); 2293 }); 2294 2295 child.replace(subtree.children); 2296 } else { 2297 child.data('resource', r); 2298 } 2299 2300 matchResources(child, syntax); 2301 }); 2302 } 2303 2304 // XXX register abbreviation filter that creates references to resources 2305 // on abbreviation nodes 2306 /** 2307 * @param {AbbreviationNode} tree 2308 */ 2309 require('abbreviationParser').addPreprocessor(function(tree, options) { 2310 var syntax = options.syntax || emmet.defaultSyntax(); 2311 matchResources(tree, syntax); 2312 }); 2313 2314 });/** 2315 * Pasted content abbreviation processor. A pasted content is a content that 2316 * should be inserted into implicitly repeated abbreviation nodes. 2317 * This processor powers “Wrap With Abbreviation” action 2318 * @param {Function} require 2319 * @param {Underscore} _ 2320 */ 2321 emmet.exec(function(require, _) { 2322 var parser = require('abbreviationParser'); 2323 var outputPlaceholder = '$#'; 2324 2325 /** 2326 * Locates output placeholders inside text 2327 * @param {String} text 2328 * @returns {Array} Array of ranges of output placeholder in text 2329 */ 2330 function locateOutputPlaceholder(text) { 2331 var range = require('range'); 2332 var result = []; 2333 2334 /** @type StringStream */ 2335 var stream = require('stringStream').create(text); 2336 2337 while (!stream.eol()) { 2338 if (stream.peek() == '\\') { 2339 stream.next(); 2340 } else { 2341 stream.start = stream.pos; 2342 if (stream.match(outputPlaceholder, true)) { 2343 result.push(range.create(stream.start, outputPlaceholder)); 2344 continue; 2345 } 2346 } 2347 stream.next(); 2348 } 2349 2350 return result; 2351 } 2352 2353 /** 2354 * Replaces output placeholders inside <code>source</code> with 2355 * <code>value</code> 2356 * @param {String} source 2357 * @param {String} value 2358 * @returns {String} 2359 */ 2360 function replaceOutputPlaceholders(source, value) { 2361 var utils = require('utils'); 2362 var ranges = locateOutputPlaceholder(source); 2363 2364 ranges.reverse(); 2365 _.each(ranges, function(r) { 2366 source = utils.replaceSubstring(source, value, r); 2367 }); 2368 2369 return source; 2370 } 2371 2372 /** 2373 * Check if parsed node contains output placeholder – a target where 2374 * pasted content should be inserted 2375 * @param {AbbreviationNode} node 2376 * @returns {Boolean} 2377 */ 2378 function hasOutputPlaceholder(node) { 2379 if (locateOutputPlaceholder(node.content).length) 2380 return true; 2381 2382 // check if attributes contains placeholder 2383 return !!_.find(node.attributeList(), function(attr) { 2384 return !!locateOutputPlaceholder(attr.value).length; 2385 }); 2386 } 2387 2388 /** 2389 * Insert pasted content into correct positions of parsed node 2390 * @param {AbbreviationNode} node 2391 * @param {String} content 2392 * @param {Boolean} overwrite Overwrite node content if no value placeholders 2393 * found instead of appending to existing content 2394 */ 2395 function insertPastedContent(node, content, overwrite) { 2396 var nodesWithPlaceholders = node.findAll(function(item) { 2397 return hasOutputPlaceholder(item); 2398 }); 2399 2400 if (hasOutputPlaceholder(node)) 2401 nodesWithPlaceholders.unshift(node); 2402 2403 if (nodesWithPlaceholders.length) { 2404 _.each(nodesWithPlaceholders, function(item) { 2405 item.content = replaceOutputPlaceholders(item.content, content); 2406 _.each(item._attributes, function(attr) { 2407 attr.value = replaceOutputPlaceholders(attr.value, content); 2408 }); 2409 }); 2410 } else { 2411 // on output placeholders in subtree, insert content in the deepest 2412 // child node 2413 var deepest = node.deepestChild() || node; 2414 if (overwrite) { 2415 deepest.content = content; 2416 } else { 2417 deepest.content = require('abbreviationUtils').insertChildContent(deepest.content, content); 2418 } 2419 } 2420 } 2421 2422 /** 2423 * @param {AbbreviationNode} tree 2424 * @param {Object} options 2425 */ 2426 parser.addPreprocessor(function(tree, options) { 2427 if (options.pastedContent) { 2428 var utils = require('utils'); 2429 var lines = _.map(utils.splitByLines(options.pastedContent, true), utils.trim); 2430 2431 // set repeat count for implicitly repeated elements before 2432 // tree is unrolled 2433 tree.findAll(function(item) { 2434 if (item.hasImplicitRepeat) { 2435 item.data('paste', lines); 2436 return item.repeatCount = lines.length; 2437 } 2438 }); 2439 } 2440 }); 2441 2442 /** 2443 * @param {AbbreviationNode} tree 2444 * @param {Object} options 2445 */ 2446 parser.addPostprocessor(function(tree, options) { 2447 // for each node with pasted content, update text data 2448 var targets = tree.findAll(function(item) { 2449 var pastedContentObj = item.data('paste'); 2450 var pastedContent = ''; 2451 if (_.isArray(pastedContentObj)) { 2452 pastedContent = pastedContentObj[item.counter - 1]; 2453 } else if (_.isFunction(pastedContentObj)) { 2454 pastedContent = pastedContentObj(item.counter - 1, item.content); 2455 } else if (pastedContentObj) { 2456 pastedContent = pastedContentObj; 2457 } 2458 2459 if (pastedContent) { 2460 insertPastedContent(item, pastedContent, !!item.data('pasteOverwrites')); 2461 } 2462 2463 item.data('paste', null); 2464 return !!pastedContentObj; 2465 }); 2466 2467 if (!targets.length && options.pastedContent) { 2468 // no implicitly repeated elements, put pasted content in 2469 // the deepest child 2470 insertPastedContent(tree, options.pastedContent); 2471 } 2472 }); 2473 });/** 2474 * Resolves tag names in abbreviations with implied name 2475 */ 2476 emmet.exec(function(require, _) { 2477 /** 2478 * Resolves implicit node names in parsed tree 2479 * @param {AbbreviationNode} tree 2480 */ 2481 function resolveNodeNames(tree) { 2482 var tagName = require('tagName'); 2483 _.each(tree.children, function(node) { 2484 if (node.hasImplicitName() || node.data('forceNameResolving')) { 2485 node._name = tagName.resolve(node.parent.name()); 2486 } 2487 resolveNodeNames(node); 2488 }); 2489 2490 return tree; 2491 } 2492 2493 require('abbreviationParser').addPostprocessor(resolveNodeNames); 2494 });/** 2495 * @author Stoyan Stefanov 2496 * @link https://github.com/stoyan/etc/tree/master/cssex 2497 */ 2498 2499 emmet.define('cssParser', function(require, _) { 2500 var walker, tokens = [], isOp, isNameChar, isDigit; 2501 2502 // walks around the source 2503 walker = { 2504 lines: null, 2505 total_lines: 0, 2506 linenum: -1, 2507 line: '', 2508 ch: '', 2509 chnum: -1, 2510 init: function (source) { 2511 var me = walker; 2512 2513 // source, yumm 2514 me.lines = source 2515 .replace(/\r\n/g, '\n') 2516 .replace(/\r/g, '\n') 2517 .split('\n'); 2518 me.total_lines = me.lines.length; 2519 2520 // reset 2521 me.chnum = -1; 2522 me.linenum = -1; 2523 me.ch = ''; 2524 me.line = ''; 2525 2526 // advance 2527 me.nextLine(); 2528 me.nextChar(); 2529 }, 2530 nextLine: function () { 2531 var me = this; 2532 me.linenum += 1; 2533 if (me.total_lines <= me.linenum) { 2534 me.line = false; 2535 } else { 2536 me.line = me.lines[me.linenum]; 2537 } 2538 if (me.chnum !== -1) { 2539 me.chnum = 0; 2540 } 2541 return me.line; 2542 }, 2543 nextChar: function () { 2544 var me = this; 2545 me.chnum += 1; 2546 while (me.line.charAt(me.chnum) === '') { 2547 if (this.nextLine() === false) { 2548 me.ch = false; 2549 return false; // end of source 2550 } 2551 me.chnum = -1; 2552 me.ch = '\n'; 2553 return '\n'; 2554 } 2555 me.ch = me.line.charAt(me.chnum); 2556 return me.ch; 2557 }, 2558 peek: function() { 2559 return this.line.charAt(this.chnum + 1); 2560 } 2561 }; 2562 2563 // utility helpers 2564 isNameChar = function (c) { 2565 // be more tolerate for name tokens: allow & character for LESS syntax 2566 return (c == '&' || c === '_' || c === '-' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); 2567 }; 2568 2569 isDigit = function (ch) { 2570 return (ch !== false && ch >= '0' && ch <= '9'); 2571 }; 2572 2573 isOp = (function () { 2574 var opsa = "{}[]()+*=.,;:>~|\\%$#@^!".split(''), 2575 opsmatcha = "*^|$~".split(''), 2576 ops = {}, 2577 opsmatch = {}, 2578 i = 0; 2579 for (; i < opsa.length; i += 1) { 2580 ops[opsa[i]] = true; 2581 } 2582 for (i = 0; i < opsmatcha.length; i += 1) { 2583 opsmatch[opsmatcha[i]] = true; 2584 } 2585 return function (ch, matchattr) { 2586 if (matchattr) { 2587 return !!opsmatch[ch]; 2588 } 2589 return !!ops[ch]; 2590 }; 2591 }()); 2592 2593 // shorthands 2594 function isset(v) { 2595 return typeof v !== 'undefined'; 2596 } 2597 function getConf() { 2598 return { 2599 'char': walker.chnum, 2600 line: walker.linenum 2601 }; 2602 } 2603 2604 2605 // creates token objects and pushes them to a list 2606 function tokener(value, type, conf) { 2607 var w = walker, c = conf || {}; 2608 tokens.push({ 2609 charstart: isset(c['char']) ? c['char'] : w.chnum, 2610 charend: isset(c.charend) ? c.charend : w.chnum, 2611 linestart: isset(c.line) ? c.line : w.linenum, 2612 lineend: isset(c.lineend) ? c.lineend : w.linenum, 2613 value: value, 2614 type: type || value 2615 }); 2616 } 2617 2618 // oops 2619 function error(m, config) { 2620 var w = walker, 2621 conf = config || {}, 2622 c = isset(conf['char']) ? conf['char'] : w.chnum, 2623 l = isset(conf.line) ? conf.line : w.linenum; 2624 return { 2625 name: "ParseError", 2626 message: m + " at line " + (l + 1) + ' char ' + (c + 1), 2627 walker: w, 2628 tokens: tokens 2629 }; 2630 } 2631 2632 2633 // token handlers follow for: 2634 // white space, comment, string, identifier, number, operator 2635 function white() { 2636 2637 var c = walker.ch, 2638 token = '', 2639 conf = getConf(); 2640 2641 while (c === " " || c === "\t") { 2642 token += c; 2643 c = walker.nextChar(); 2644 } 2645 2646 tokener(token, 'white', conf); 2647 2648 } 2649 2650 function comment() { 2651 2652 var w = walker, 2653 c = w.ch, 2654 token = c, 2655 cnext, 2656 conf = getConf(); 2657 2658 cnext = w.nextChar(); 2659 2660 if (cnext === '/') { 2661 // inline comment in SCSS and such 2662 token += cnext; 2663 var pk = w.peek(); 2664 while (pk && pk !== '\n') { 2665 token += cnext; 2666 cnext = w.nextChar(); 2667 pk = w.peek(); 2668 } 2669 } else if (cnext === '*') { 2670 // multiline CSS commment 2671 while (!(c === "*" && cnext === "/")) { 2672 token += cnext; 2673 c = cnext; 2674 cnext = w.nextChar(); 2675 } 2676 } else { 2677 // oops, not a comment, just a / 2678 conf.charend = conf['char']; 2679 conf.lineend = conf.line; 2680 return tokener(token, token, conf); 2681 } 2682 2683 token += cnext; 2684 w.nextChar(); 2685 tokener(token, 'comment', conf); 2686 } 2687 2688 function str() { 2689 var w = walker, 2690 c = w.ch, 2691 q = c, 2692 token = c, 2693 cnext, 2694 conf = getConf(); 2695 2696 c = w.nextChar(); 2697 2698 while (c !== q) { 2699 2700 if (c === '\n') { 2701 cnext = w.nextChar(); 2702 if (cnext === "\\") { 2703 token += c + cnext; 2704 } else { 2705 // end of line with no \ escape = bad 2706 throw error("Unterminated string", conf); 2707 } 2708 } else { 2709 if (c === "\\") { 2710 token += c + w.nextChar(); 2711 } else { 2712 token += c; 2713 } 2714 } 2715 2716 c = w.nextChar(); 2717 2718 } 2719 token += c; 2720 w.nextChar(); 2721 tokener(token, 'string', conf); 2722 } 2723 2724 function brace() { 2725 var w = walker, 2726 c = w.ch, 2727 depth = 0, 2728 token = c, 2729 conf = getConf(); 2730 2731 c = w.nextChar(); 2732 2733 while (c !== ')' && !depth) { 2734 if (c === '(') { 2735 depth++; 2736 } else if (c === ')') { 2737 depth--; 2738 } else if (c === false) { 2739 throw error("Unterminated brace", conf); 2740 } 2741 2742 token += c; 2743 c = w.nextChar(); 2744 } 2745 2746 token += c; 2747 w.nextChar(); 2748 tokener(token, 'brace', conf); 2749 } 2750 2751 function identifier(pre) { 2752 var w = walker, 2753 c = w.ch, 2754 conf = getConf(), 2755 token = (pre) ? pre + c : c; 2756 2757 c = w.nextChar(); 2758 2759 if (pre) { // adjust token position 2760 conf['char'] -= pre.length; 2761 } 2762 2763 while (isNameChar(c) || isDigit(c)) { 2764 token += c; 2765 c = w.nextChar(); 2766 } 2767 2768 tokener(token, 'identifier', conf); 2769 } 2770 2771 function num() { 2772 var w = walker, 2773 c = w.ch, 2774 conf = getConf(), 2775 token = c, 2776 point = token === '.', 2777 nondigit; 2778 2779 c = w.nextChar(); 2780 nondigit = !isDigit(c); 2781 2782 // .2px or .classname? 2783 if (point && nondigit) { 2784 // meh, NaN, could be a class name, so it's an operator for now 2785 conf.charend = conf['char']; 2786 conf.lineend = conf.line; 2787 return tokener(token, '.', conf); 2788 } 2789 2790 // -2px or -moz-something 2791 if (token === '-' && nondigit) { 2792 return identifier('-'); 2793 } 2794 2795 while (c !== false && (isDigit(c) || (!point && c === '.'))) { // not end of source && digit or first instance of . 2796 if (c === '.') { 2797 point = true; 2798 } 2799 token += c; 2800 c = w.nextChar(); 2801 } 2802 2803 tokener(token, 'number', conf); 2804 2805 } 2806 2807 function op() { 2808 var w = walker, 2809 c = w.ch, 2810 conf = getConf(), 2811 token = c, 2812 next = w.nextChar(); 2813 2814 if (next === "=" && isOp(token, true)) { 2815 token += next; 2816 tokener(token, 'match', conf); 2817 w.nextChar(); 2818 return; 2819 } 2820 2821 conf.charend = conf['char'] + 1; 2822 conf.lineend = conf.line; 2823 tokener(token, token, conf); 2824 } 2825 2826 2827 // call the appropriate handler based on the first character in a token suspect 2828 function tokenize() { 2829 2830 var ch = walker.ch; 2831 2832 if (ch === " " || ch === "\t") { 2833 return white(); 2834 } 2835 2836 if (ch === '/') { 2837 return comment(); 2838 } 2839 2840 if (ch === '"' || ch === "'") { 2841 return str(); 2842 } 2843 2844 if (ch === '(') { 2845 return brace(); 2846 } 2847 2848 if (ch === '-' || ch === '.' || isDigit(ch)) { // tricky - char: minus (-1px) or dash (-moz-stuff) 2849 return num(); 2850 } 2851 2852 if (isNameChar(ch)) { 2853 return identifier(); 2854 } 2855 2856 if (isOp(ch)) { 2857 return op(); 2858 } 2859 2860 if (ch === "\n") { 2861 tokener("line"); 2862 walker.nextChar(); 2863 return; 2864 } 2865 2866 throw error("Unrecognized character"); 2867 } 2868 2869 /** 2870 * Returns newline character at specified position in content 2871 * @param {String} content 2872 * @param {Number} pos 2873 * @return {String} 2874 */ 2875 function getNewline(content, pos) { 2876 return content.charAt(pos) == '\r' && content.charAt(pos + 1) == '\n' 2877 ? '\r\n' 2878 : content.charAt(pos); 2879 } 2880 2881 return { 2882 /** 2883 * @param source 2884 * @returns 2885 * @memberOf emmet.cssParser 2886 */ 2887 lex: function (source) { 2888 walker.init(source); 2889 tokens = []; 2890 while (walker.ch !== false) { 2891 tokenize(); 2892 } 2893 return tokens; 2894 }, 2895 2896 /** 2897 * Tokenizes CSS source 2898 * @param {String} source 2899 * @returns {Array} 2900 */ 2901 parse: function(source) { 2902 // transform tokens 2903 var pos = 0; 2904 return _.map(this.lex(source), function(token) { 2905 if (token.type == 'line') { 2906 token.value = getNewline(source, pos); 2907 } 2908 2909 return { 2910 type: token.type, 2911 start: pos, 2912 end: (pos += token.value.length) 2913 }; 2914 }); 2915 }, 2916 2917 toSource: function (toks) { 2918 var i = 0, max = toks.length, t, src = ''; 2919 for (; i < max; i += 1) { 2920 t = toks[i]; 2921 if (t.type === 'line') { 2922 src += '\n'; 2923 } else { 2924 src += t.value; 2925 } 2926 } 2927 return src; 2928 } 2929 }; 2930 });/** 2931 * HTML tokenizer by Marijn Haverbeke 2932 * http://codemirror.net/ 2933 * @constructor 2934 * @memberOf __xmlParseDefine 2935 * @param {Function} require 2936 * @param {Underscore} _ 2937 */ 2938 emmet.define('xmlParser', function(require, _) { 2939 var Kludges = { 2940 autoSelfClosers : {}, 2941 implicitlyClosed : {}, 2942 contextGrabbers : {}, 2943 doNotIndent : {}, 2944 allowUnquoted : true, 2945 allowMissing : true 2946 }; 2947 2948 // Return variables for tokenizers 2949 var tagName = null, type = null; 2950 2951 function inText(stream, state) { 2952 function chain(parser) { 2953 state.tokenize = parser; 2954 return parser(stream, state); 2955 } 2956 2957 var ch = stream.next(); 2958 if (ch == "<") { 2959 if (stream.eat("!")) { 2960 if (stream.eat("[")) { 2961 if (stream.match("CDATA[")) 2962 return chain(inBlock("atom", "]]>")); 2963 else 2964 return null; 2965 } else if (stream.match("--")) 2966 return chain(inBlock("comment", "-->")); 2967 else if (stream.match("DOCTYPE", true, true)) { 2968 stream.eatWhile(/[\w\._\-]/); 2969 return chain(doctype(1)); 2970 } else 2971 return null; 2972 } else if (stream.eat("?")) { 2973 stream.eatWhile(/[\w\._\-]/); 2974 state.tokenize = inBlock("meta", "?>"); 2975 return "meta"; 2976 } else { 2977 type = stream.eat("/") ? "closeTag" : "openTag"; 2978 stream.eatSpace(); 2979 tagName = ""; 2980 var c; 2981 while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) 2982 tagName += c; 2983 state.tokenize = inTag; 2984 return "tag"; 2985 } 2986 } else if (ch == "&") { 2987 var ok; 2988 if (stream.eat("#")) { 2989 if (stream.eat("x")) { 2990 ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 2991 } else { 2992 ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 2993 } 2994 } else { 2995 ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 2996 } 2997 return ok ? "atom" : "error"; 2998 } else { 2999 stream.eatWhile(/[^&<]/); 3000 return "text"; 3001 } 3002 } 3003 3004 function inTag(stream, state) { 3005 var ch = stream.next(); 3006 if (ch == ">" || (ch == "/" && stream.eat(">"))) { 3007 state.tokenize = inText; 3008 type = ch == ">" ? "endTag" : "selfcloseTag"; 3009 return "tag"; 3010 } else if (ch == "=") { 3011 type = "equals"; 3012 return null; 3013 } else if (/[\'\"]/.test(ch)) { 3014 state.tokenize = inAttribute(ch); 3015 return state.tokenize(stream, state); 3016 } else { 3017 stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); 3018 return "word"; 3019 } 3020 } 3021 3022 function inAttribute(quote) { 3023 return function(stream, state) { 3024 while (!stream.eol()) { 3025 if (stream.next() == quote) { 3026 state.tokenize = inTag; 3027 break; 3028 } 3029 } 3030 return "string"; 3031 }; 3032 } 3033 3034 function inBlock(style, terminator) { 3035 return function(stream, state) { 3036 while (!stream.eol()) { 3037 if (stream.match(terminator)) { 3038 state.tokenize = inText; 3039 break; 3040 } 3041 stream.next(); 3042 } 3043 return style; 3044 }; 3045 } 3046 3047 function doctype(depth) { 3048 return function(stream, state) { 3049 var ch; 3050 while ((ch = stream.next()) !== null) { 3051 if (ch == "<") { 3052 state.tokenize = doctype(depth + 1); 3053 return state.tokenize(stream, state); 3054 } else if (ch == ">") { 3055 if (depth == 1) { 3056 state.tokenize = inText; 3057 break; 3058 } else { 3059 state.tokenize = doctype(depth - 1); 3060 return state.tokenize(stream, state); 3061 } 3062 } 3063 } 3064 return "meta"; 3065 }; 3066 } 3067 3068 var curState = null, setStyle; 3069 function pass() { 3070 for (var i = arguments.length - 1; i >= 0; i--) 3071 curState.cc.push(arguments[i]); 3072 } 3073 3074 function cont() { 3075 pass.apply(null, arguments); 3076 return true; 3077 } 3078 3079 function pushContext(tagName, startOfLine) { 3080 var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) 3081 || (curState.context && curState.context.noIndent); 3082 curState.context = { 3083 prev : curState.context, 3084 tagName : tagName, 3085 indent : curState.indented, 3086 startOfLine : startOfLine, 3087 noIndent : noIndent 3088 }; 3089 } 3090 3091 function popContext() { 3092 if (curState.context) 3093 curState.context = curState.context.prev; 3094 } 3095 3096 function element(type) { 3097 if (type == "openTag") { 3098 curState.tagName = tagName; 3099 return cont(attributes, endtag(curState.startOfLine)); 3100 } else if (type == "closeTag") { 3101 var err = false; 3102 if (curState.context) { 3103 if (curState.context.tagName != tagName) { 3104 if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { 3105 popContext(); 3106 } 3107 err = !curState.context || curState.context.tagName != tagName; 3108 } 3109 } else { 3110 err = true; 3111 } 3112 3113 if (err) 3114 setStyle = "error"; 3115 return cont(endclosetag(err)); 3116 } 3117 return cont(); 3118 } 3119 3120 function endtag(startOfLine) { 3121 return function(type) { 3122 if (type == "selfcloseTag" 3123 || (type == "endTag" && Kludges.autoSelfClosers 3124 .hasOwnProperty(curState.tagName 3125 .toLowerCase()))) { 3126 maybePopContext(curState.tagName.toLowerCase()); 3127 return cont(); 3128 } 3129 if (type == "endTag") { 3130 maybePopContext(curState.tagName.toLowerCase()); 3131 pushContext(curState.tagName, startOfLine); 3132 return cont(); 3133 } 3134 return cont(); 3135 }; 3136 } 3137 3138 function endclosetag(err) { 3139 return function(type) { 3140 if (err) 3141 setStyle = "error"; 3142 if (type == "endTag") { 3143 popContext(); 3144 return cont(); 3145 } 3146 setStyle = "error"; 3147 return cont(arguments.callee); 3148 }; 3149 } 3150 3151 function maybePopContext(nextTagName) { 3152 var parentTagName; 3153 while (true) { 3154 if (!curState.context) { 3155 return; 3156 } 3157 parentTagName = curState.context.tagName.toLowerCase(); 3158 if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) 3159 || !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { 3160 return; 3161 } 3162 popContext(); 3163 } 3164 } 3165 3166 function attributes(type) { 3167 if (type == "word") { 3168 setStyle = "attribute"; 3169 return cont(attribute, attributes); 3170 } 3171 if (type == "endTag" || type == "selfcloseTag") 3172 return pass(); 3173 setStyle = "error"; 3174 return cont(attributes); 3175 } 3176 3177 function attribute(type) { 3178 if (type == "equals") 3179 return cont(attvalue, attributes); 3180 if (!Kludges.allowMissing) 3181 setStyle = "error"; 3182 return (type == "endTag" || type == "selfcloseTag") ? pass() 3183 : cont(); 3184 } 3185 3186 function attvalue(type) { 3187 if (type == "string") 3188 return cont(attvaluemaybe); 3189 if (type == "word" && Kludges.allowUnquoted) { 3190 setStyle = "string"; 3191 return cont(); 3192 } 3193 setStyle = "error"; 3194 return (type == "endTag" || type == "selfCloseTag") ? pass() 3195 : cont(); 3196 } 3197 3198 function attvaluemaybe(type) { 3199 if (type == "string") 3200 return cont(attvaluemaybe); 3201 else 3202 return pass(); 3203 } 3204 3205 function startState() { 3206 return { 3207 tokenize : inText, 3208 cc : [], 3209 indented : 0, 3210 startOfLine : true, 3211 tagName : null, 3212 context : null 3213 }; 3214 } 3215 3216 function token(stream, state) { 3217 if (stream.sol()) { 3218 state.startOfLine = true; 3219 state.indented = 0; 3220 } 3221 3222 if (stream.eatSpace()) 3223 return null; 3224 3225 setStyle = type = tagName = null; 3226 var style = state.tokenize(stream, state); 3227 state.type = type; 3228 if ((style || type) && style != "comment") { 3229 curState = state; 3230 while (true) { 3231 var comb = state.cc.pop() || element; 3232 if (comb(type || style)) 3233 break; 3234 } 3235 } 3236 state.startOfLine = false; 3237 return setStyle || style; 3238 } 3239 3240 return { 3241 /** 3242 * @memberOf emmet.xmlParser 3243 * @returns 3244 */ 3245 parse: function(data, offset) { 3246 offset = offset || 0; 3247 var state = startState(); 3248 var stream = require('stringStream').create(data); 3249 var tokens = []; 3250 while (!stream.eol()) { 3251 tokens.push({ 3252 type: token(stream, state), 3253 start: stream.start + offset, 3254 end: stream.pos + offset 3255 }); 3256 stream.start = stream.pos; 3257 } 3258 3259 return tokens; 3260 } 3261 }; 3262 }); 3263 /*! 3264 * string_score.js: String Scoring Algorithm 0.1.10 3265 * 3266 * http://joshaven.com/string_score 3267 * https://github.com/joshaven/string_score 3268 * 3269 * Copyright (C) 2009-2011 Joshaven Potter <yourtech@gmail.com> 3270 * Special thanks to all of the contributors listed here https://github.com/joshaven/string_score 3271 * MIT license: http://www.opensource.org/licenses/mit-license.php 3272 * 3273 * Date: Tue Mar 1 2011 3274 */ 3275 3276 /** 3277 * Scores a string against another string. 3278 * 'Hello World'.score('he'); //=> 0.5931818181818181 3279 * 'Hello World'.score('Hello'); //=> 0.7318181818181818 3280 */ 3281 emmet.define('string-score', function(require, _) { 3282 return { 3283 score: function(string, abbreviation, fuzziness) { 3284 // If the string is equal to the abbreviation, perfect match. 3285 if (string == abbreviation) {return 1;} 3286 //if it's not a perfect match and is empty return 0 3287 if(abbreviation == "") {return 0;} 3288 3289 var total_character_score = 0, 3290 abbreviation_length = abbreviation.length, 3291 string_length = string.length, 3292 start_of_string_bonus, 3293 abbreviation_score, 3294 fuzzies=1, 3295 final_score; 3296 3297 // Walk through abbreviation and add up scores. 3298 for (var i = 0, 3299 character_score/* = 0*/, 3300 index_in_string/* = 0*/, 3301 c/* = ''*/, 3302 index_c_lowercase/* = 0*/, 3303 index_c_uppercase/* = 0*/, 3304 min_index/* = 0*/; 3305 i < abbreviation_length; 3306 ++i) { 3307 3308 // Find the first case-insensitive match of a character. 3309 c = abbreviation.charAt(i); 3310 3311 index_c_lowercase = string.indexOf(c.toLowerCase()); 3312 index_c_uppercase = string.indexOf(c.toUpperCase()); 3313 min_index = Math.min(index_c_lowercase, index_c_uppercase); 3314 index_in_string = (min_index > -1) ? min_index : Math.max(index_c_lowercase, index_c_uppercase); 3315 3316 if (index_in_string === -1) { 3317 if (fuzziness) { 3318 fuzzies += 1-fuzziness; 3319 continue; 3320 } else { 3321 return 0; 3322 } 3323 } else { 3324 character_score = 0.1; 3325 } 3326 3327 // Set base score for matching 'c'. 3328 3329 // Same case bonus. 3330 if (string[index_in_string] === c) { 3331 character_score += 0.1; 3332 } 3333 3334 // Consecutive letter & start-of-string Bonus 3335 if (index_in_string === 0) { 3336 // Increase the score when matching first character of the remainder of the string 3337 character_score += 0.6; 3338 if (i === 0) { 3339 // If match is the first character of the string 3340 // & the first character of abbreviation, add a 3341 // start-of-string match bonus. 3342 start_of_string_bonus = 1; //true; 3343 } 3344 } 3345 else { 3346 // Acronym Bonus 3347 // Weighing Logic: Typing the first character of an acronym is as if you 3348 // preceded it with two perfect character matches. 3349 if (string.charAt(index_in_string - 1) === ' ') { 3350 character_score += 0.8; // * Math.min(index_in_string, 5); // Cap bonus at 0.4 * 5 3351 } 3352 } 3353 3354 // Left trim the already matched part of the string 3355 // (forces sequential matching). 3356 string = string.substring(index_in_string + 1, string_length); 3357 3358 total_character_score += character_score; 3359 } // end of for loop 3360 3361 // Uncomment to weigh smaller words higher. 3362 // return total_character_score / string_length; 3363 3364 abbreviation_score = total_character_score / abbreviation_length; 3365 //percentage_of_matched_string = abbreviation_length / string_length; 3366 //word_score = abbreviation_score * percentage_of_matched_string; 3367 3368 // Reduce penalty for longer strings. 3369 //final_score = (word_score + abbreviation_score) / 2; 3370 final_score = ((abbreviation_score * (abbreviation_length / string_length)) + abbreviation_score) / 2; 3371 3372 final_score = final_score / fuzzies; 3373 3374 if (start_of_string_bonus && (final_score + 0.15 < 1)) { 3375 final_score += 0.15; 3376 } 3377 3378 return final_score; 3379 } 3380 }; 3381 });/** 3382 * Utility module for Emmet 3383 * @param {Function} require 3384 * @param {Underscore} _ 3385 */ 3386 emmet.define('utils', function(require, _) { 3387 /** 3388 * Special token used as a placeholder for caret positions inside 3389 * generated output 3390 */ 3391 var caretPlaceholder = '${0}'; 3392 3393 return { 3394 /** @memberOf utils */ 3395 reTag: /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/, 3396 3397 /** 3398 * Test if passed string ends with XHTML tag. This method is used for testing 3399 * '>' character: it belongs to tag or it's a part of abbreviation? 3400 * @param {String} str 3401 * @return {Boolean} 3402 */ 3403 endsWithTag: function(str) { 3404 return this.reTag.test(str); 3405 }, 3406 3407 /** 3408 * Check if passed symbol is a number 3409 * @param {String} ch 3410 * @returns {Boolean} 3411 */ 3412 isNumeric: function(ch) { 3413 if (typeof(ch) == 'string') 3414 ch = ch.charCodeAt(0); 3415 3416 return (ch && ch > 47 && ch < 58); 3417 }, 3418 3419 /** 3420 * Trim whitespace from string 3421 * @param {String} text 3422 * @return {String} 3423 */ 3424 trim: function(text) { 3425 return (text || "").replace(/^\s+|\s+$/g, ""); 3426 }, 3427 3428 /** 3429 * Returns newline character 3430 * @returns {String} 3431 */ 3432 getNewline: function() { 3433 var res = require('resources'); 3434 if (!res) { 3435 return '\n'; 3436 } 3437 3438 var nl = res.getVariable('newline'); 3439 return _.isString(nl) ? nl : '\n'; 3440 }, 3441 3442 /** 3443 * Sets new newline character that will be used in output 3444 * @param {String} str 3445 */ 3446 setNewline: function(str) { 3447 var res = require('resources'); 3448 res.setVariable('newline', str); 3449 res.setVariable('nl', str); 3450 }, 3451 3452 /** 3453 * Split text into lines. Set <code>remove_empty</code> to true to filter 3454 * empty lines 3455 * @param {String} text Text to split 3456 * @param {Boolean} removeEmpty Remove empty lines from result 3457 * @return {Array} 3458 */ 3459 splitByLines: function(text, removeEmpty) { 3460 // IE fails to split string by regexp, 3461 // need to normalize newlines first 3462 // Also, Mozilla's Rhiho JS engine has a weird newline bug 3463 var nl = this.getNewline(); 3464 var lines = (text || '') 3465 .replace(/\r\n/g, '\n') 3466 .replace(/\n\r/g, '\n') 3467 .replace(/\r/g, '\n') 3468 .replace(/\n/g, nl) 3469 .split(nl); 3470 3471 if (removeEmpty) { 3472 lines = _.filter(lines, function(line) { 3473 return line.length && !!this.trim(line); 3474 }, this); 3475 } 3476 3477 return lines; 3478 }, 3479 3480 /** 3481 * Normalizes newline character: replaces newlines in <code>text</code> 3482 * with newline defined in preferences 3483 * @param {String} text 3484 * @returns {String} 3485 */ 3486 normalizeNewline: function(text) { 3487 return this.splitByLines(text).join(this.getNewline()); 3488 }, 3489 3490 /** 3491 * Repeats string <code>howMany</code> times 3492 * @param {String} str 3493 * @param {Number} how_many 3494 * @return {String} 3495 */ 3496 repeatString: function(str, howMany) { 3497 var result = []; 3498 3499 for (var i = 0; i < howMany; i++) 3500 result.push(str); 3501 3502 return result.join(''); 3503 }, 3504 3505 /** 3506 * Returns list of paddings that should be used to align passed string 3507 * @param {Array} strings 3508 * @returns {Array} 3509 */ 3510 getStringsPads: function(strings) { 3511 var lengths = _.map(strings, function(s) { 3512 return _.isString(s) ? s.length : +s; 3513 }); 3514 3515 var max = _.max(lengths); 3516 return _.map(lengths, function(l) { 3517 var pad = max - l; 3518 return pad ? this.repeatString(' ', pad) : ''; 3519 }, this); 3520 }, 3521 3522 /** 3523 * Indents text with padding 3524 * @param {String} text Text to indent 3525 * @param {String} pad Padding size (number) or padding itself (string) 3526 * @return {String} 3527 */ 3528 padString: function(text, pad) { 3529 var padStr = (_.isNumber(pad)) 3530 ? this.repeatString(require('resources').getVariable('indentation') || '\t', pad) 3531 : pad; 3532 3533 var result = []; 3534 3535 var lines = this.splitByLines(text); 3536 var nl = this.getNewline(); 3537 3538 result.push(lines[0]); 3539 for (var j = 1; j < lines.length; j++) 3540 result.push(nl + padStr + lines[j]); 3541 3542 return result.join(''); 3543 }, 3544 3545 /** 3546 * Pad string with zeroes 3547 * @param {String} str String to pad 3548 * @param {Number} pad Desired string length 3549 * @return {String} 3550 */ 3551 zeroPadString: function(str, pad) { 3552 var padding = ''; 3553 var il = str.length; 3554 3555 while (pad > il++) padding += '0'; 3556 return padding + str; 3557 }, 3558 3559 /** 3560 * Removes padding at the beginning of each text's line 3561 * @param {String} text 3562 * @param {String} pad 3563 */ 3564 unindentString: function(text, pad) { 3565 var lines = this.splitByLines(text); 3566 for (var i = 0; i < lines.length; i++) { 3567 if (lines[i].search(pad) === 0) 3568 lines[i] = lines[i].substr(pad.length); 3569 } 3570 3571 return lines.join(this.getNewline()); 3572 }, 3573 3574 /** 3575 * Replaces unescaped symbols in <code>str</code>. For example, the '$' symbol 3576 * will be replaced in 'item$count', but not in 'item\$count'. 3577 * @param {String} str Original string 3578 * @param {String} symbol Symbol to replace 3579 * @param {String} replace Symbol replacement. Might be a function that 3580 * returns new value 3581 * @return {String} 3582 */ 3583 replaceUnescapedSymbol: function(str, symbol, replace) { 3584 var i = 0; 3585 var il = str.length; 3586 var sl = symbol.length; 3587 var matchCount = 0; 3588 3589 while (i < il) { 3590 if (str.charAt(i) == '\\') { 3591 // escaped symbol, skip next character 3592 i += sl + 1; 3593 } else if (str.substr(i, sl) == symbol) { 3594 // have match 3595 var curSl = sl; 3596 matchCount++; 3597 var newValue = replace; 3598 if (_.isFunction(replace)) { 3599 var replaceData = replace(str, symbol, i, matchCount); 3600 if (replaceData) { 3601 curSl = replaceData[0].length; 3602 newValue = replaceData[1]; 3603 } else { 3604 newValue = false; 3605 } 3606 } 3607 3608 if (newValue === false) { // skip replacement 3609 i++; 3610 continue; 3611 } 3612 3613 str = str.substring(0, i) + newValue + str.substring(i + curSl); 3614 // adjust indexes 3615 il = str.length; 3616 i += newValue.length; 3617 } else { 3618 i++; 3619 } 3620 } 3621 3622 return str; 3623 }, 3624 3625 /** 3626 * Replace variables like ${var} in string 3627 * @param {String} str 3628 * @param {Object} vars Variable set (defaults to variables defined in 3629 * <code>snippets.json</code>) or variable resolver (<code>Function</code>) 3630 * @return {String} 3631 */ 3632 replaceVariables: function(str, vars) { 3633 vars = vars || {}; 3634 var resolver = _.isFunction(vars) ? vars : function(str, p1) { 3635 return p1 in vars ? vars[p1] : null; 3636 }; 3637 3638 var res = require('resources'); 3639 return require('tabStops').processText(str, { 3640 variable: function(data) { 3641 var newValue = resolver(data.token, data.name, data); 3642 if (newValue === null) { 3643 // try to find variable in resources 3644 newValue = res.getVariable(data.name); 3645 } 3646 3647 if (newValue === null || _.isUndefined(newValue)) 3648 // nothing found, return token itself 3649 newValue = data.token; 3650 return newValue; 3651 } 3652 }); 3653 }, 3654 3655 /** 3656 * Replaces '$' character in string assuming it might be escaped with '\' 3657 * @param {String} str String where character should be replaced 3658 * @param {String} value New value 3659 * @return {String} 3660 */ 3661 replaceCounter: function(str, value, total) { 3662 var symbol = '$'; 3663 // in case we received strings from Java, convert the to native strings 3664 str = String(str); 3665 value = String(value); 3666 3667 if (/^\-?\d+$/.test(value)) { 3668 value = +value; 3669 } 3670 3671 var that = this; 3672 3673 return this.replaceUnescapedSymbol(str, symbol, function(str, symbol, pos, matchNum){ 3674 if (str.charAt(pos + 1) == '{' || that.isNumeric(str.charAt(pos + 1)) ) { 3675 // it's a variable, skip it 3676 return false; 3677 } 3678 3679 // replace sequense of $ symbols with padded number 3680 var j = pos + 1; 3681 while(str.charAt(j) == '$' && str.charAt(j + 1) != '{') j++; 3682 var pad = j - pos; 3683 3684 // get counter base 3685 var base = 0, decrement = false, m; 3686 if ((m = str.substr(j).match(/^@(\-?)(\d*)/))) { 3687 j += m[0].length; 3688 3689 if (m[1]) { 3690 decrement = true; 3691 } 3692 3693 base = parseInt(m[2] || 1, 10) - 1; 3694 } 3695 3696 if (decrement && total && _.isNumber(value)) { 3697 value = total - value + 1; 3698 } 3699 3700 value += base; 3701 3702 return [str.substring(pos, j), that.zeroPadString(value + '', pad)]; 3703 }); 3704 }, 3705 3706 /** 3707 * Check if string matches against <code>reTag</code> regexp. This 3708 * function may be used to test if provided string contains HTML tags 3709 * @param {String} str 3710 * @returns {Boolean} 3711 */ 3712 matchesTag: function(str) { 3713 return this.reTag.test(str || ''); 3714 }, 3715 3716 /** 3717 * Escapes special characters used in Emmet, like '$', '|', etc. 3718 * Use this method before passing to actions like "Wrap with Abbreviation" 3719 * to make sure that existing special characters won't be altered 3720 * @param {String} text 3721 * @return {String} 3722 */ 3723 escapeText: function(text) { 3724 return text.replace(/([\$\\])/g, '\\$1'); 3725 }, 3726 3727 /** 3728 * Unescapes special characters used in Emmet, like '$', '|', etc. 3729 * @param {String} text 3730 * @return {String} 3731 */ 3732 unescapeText: function(text) { 3733 return text.replace(/\\(.)/g, '$1'); 3734 }, 3735 3736 /** 3737 * Returns caret placeholder 3738 * @returns {String} 3739 */ 3740 getCaretPlaceholder: function() { 3741 return _.isFunction(caretPlaceholder) 3742 ? caretPlaceholder.apply(this, arguments) 3743 : caretPlaceholder; 3744 }, 3745 3746 /** 3747 * Sets new representation for carets in generated output 3748 * @param {String} value New caret placeholder. Might be a 3749 * <code>Function</code> 3750 */ 3751 setCaretPlaceholder: function(value) { 3752 caretPlaceholder = value; 3753 }, 3754 3755 /** 3756 * Returns line padding 3757 * @param {String} line 3758 * @return {String} 3759 */ 3760 getLinePadding: function(line) { 3761 return (line.match(/^(\s+)/) || [''])[0]; 3762 }, 3763 3764 /** 3765 * Helper function that returns padding of line of <code>pos</code> 3766 * position in <code>content</code> 3767 * @param {String} content 3768 * @param {Number} pos 3769 * @returns {String} 3770 */ 3771 getLinePaddingFromPosition: function(content, pos) { 3772 var lineRange = this.findNewlineBounds(content, pos); 3773 return this.getLinePadding(lineRange.substring(content)); 3774 }, 3775 3776 /** 3777 * Escape special regexp chars in string, making it usable for creating dynamic 3778 * regular expressions 3779 * @param {String} str 3780 * @return {String} 3781 */ 3782 escapeForRegexp: function(str) { 3783 var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\ 3784 return str.replace(specials, "\\$&"); 3785 }, 3786 3787 /** 3788 * Make decimal number look good: convert it to fixed precision end remove 3789 * traling zeroes 3790 * @param {Number} num 3791 * @param {Number} fracion Fraction numbers (default is 2) 3792 * @return {String} 3793 */ 3794 prettifyNumber: function(num, fraction) { 3795 return num.toFixed(typeof fraction == 'undefined' ? 2 : fraction).replace(/\.?0+$/, ''); 3796 }, 3797 3798 /** 3799 * Replace substring of <code>str</code> with <code>value</code> 3800 * @param {String} str String where to replace substring 3801 * @param {String} value New substring value 3802 * @param {Number} start Start index of substring to replace. May also 3803 * be a <code>Range</code> object: in this case, the <code>end</code> 3804 * argument is not required 3805 * @param {Number} end End index of substring to replace. If ommited, 3806 * <code>start</code> argument is used 3807 */ 3808 replaceSubstring: function(str, value, start, end) { 3809 if (_.isObject(start) && 'end' in start) { 3810 end = start.end; 3811 start = start.start; 3812 } 3813 3814 if (_.isString(end)) 3815 end = start + end.length; 3816 3817 if (_.isUndefined(end)) 3818 end = start; 3819 3820 if (start < 0 || start > str.length) 3821 return str; 3822 3823 return str.substring(0, start) + value + str.substring(end); 3824 }, 3825 3826 /** 3827 * Narrows down text range, adjusting selection to non-space characters 3828 * @param {String} text 3829 * @param {Number} start Starting range in <code>text</code> where 3830 * slection should be adjusted. Can also be any object that is accepted 3831 * by <code>Range</code> class 3832 * @return {Range} 3833 */ 3834 narrowToNonSpace: function(text, start, end) { 3835 var range = require('range').create(start, end); 3836 3837 var reSpace = /[\s\n\r\u00a0]/; 3838 // narrow down selection until first non-space character 3839 while (range.start < range.end) { 3840 if (!reSpace.test(text.charAt(range.start))) 3841 break; 3842 3843 range.start++; 3844 } 3845 3846 while (range.end > range.start) { 3847 range.end--; 3848 if (!reSpace.test(text.charAt(range.end))) { 3849 range.end++; 3850 break; 3851 } 3852 } 3853 3854 return range; 3855 }, 3856 3857 /** 3858 * Find start and end index of text line for <code>from</code> index 3859 * @param {String} text 3860 * @param {Number} from 3861 */ 3862 findNewlineBounds: function(text, from) { 3863 var len = text.length, 3864 start = 0, 3865 end = len - 1, 3866 ch; 3867 3868 3869 // search left 3870 for (var i = from - 1; i > 0; i--) { 3871 ch = text.charAt(i); 3872 if (ch == '\n' || ch == '\r') { 3873 start = i + 1; 3874 break; 3875 } 3876 } 3877 // search right 3878 for (var j = from; j < len; j++) { 3879 ch = text.charAt(j); 3880 if (ch == '\n' || ch == '\r') { 3881 end = j; 3882 break; 3883 } 3884 } 3885 3886 return require('range').create(start, end - start); 3887 }, 3888 3889 /** 3890 * Deep merge of two or more objects. Taken from jQuery.extend() 3891 */ 3892 deepMerge: function() { 3893 var options, name, src, copy, copyIsArray, clone, 3894 target = arguments[0] || {}, 3895 i = 1, 3896 length = arguments.length; 3897 3898 3899 // Handle case when target is a string or something (possible in deep copy) 3900 if (!_.isObject(target) && !_.isFunction(target)) { 3901 target = {}; 3902 } 3903 3904 for ( ; i < length; i++ ) { 3905 // Only deal with non-null/undefined values 3906 if ( (options = arguments[ i ]) !== null ) { 3907 // Extend the base object 3908 for ( name in options ) { 3909 src = target[ name ]; 3910 copy = options[ name ]; 3911 3912 // Prevent never-ending loop 3913 if ( target === copy ) { 3914 continue; 3915 } 3916 3917 // Recurse if we're merging plain objects or arrays 3918 if ( copy && ( _.isObject(copy) || (copyIsArray = _.isArray(copy)) ) ) { 3919 if ( copyIsArray ) { 3920 copyIsArray = false; 3921 clone = src && _.isArray(src) ? src : []; 3922 3923 } else { 3924 clone = src && _.isObject(src) ? src : {}; 3925 } 3926 3927 // Never move original objects, clone them 3928 target[ name ] = this.deepMerge(clone, copy ); 3929 3930 // Don't bring in undefined values 3931 } else if ( copy !== undefined ) { 3932 target[ name ] = copy; 3933 } 3934 } 3935 } 3936 } 3937 3938 // Return the modified object 3939 return target; 3940 } 3941 }; 3942 }); 3943 /** 3944 * Helper module to work with ranges 3945 * @constructor 3946 * @memberOf __rangeDefine 3947 * @param {Function} require 3948 * @param {Underscore} _ 3949 */ 3950 emmet.define('range', function(require, _) { 3951 function cmp(a, b, op) { 3952 switch (op) { 3953 case 'eq': 3954 case '==': 3955 return a === b; 3956 case 'lt': 3957 case '<': 3958 return a < b; 3959 case 'lte': 3960 case '<=': 3961 return a <= b; 3962 case 'gt': 3963 case '>': 3964 return a > b; 3965 case 'gte': 3966 case '>=': 3967 return a >= b; 3968 } 3969 } 3970 3971 3972 /** 3973 * @type Range 3974 * @constructor 3975 * @param {Object} start 3976 * @param {Number} len 3977 */ 3978 function Range(start, len) { 3979 if (_.isObject(start) && 'start' in start) { 3980 // create range from object stub 3981 this.start = Math.min(start.start, start.end); 3982 this.end = Math.max(start.start, start.end); 3983 } else if (_.isArray(start)) { 3984 this.start = start[0]; 3985 this.end = start[1]; 3986 } else { 3987 len = _.isString(len) ? len.length : +len; 3988 this.start = start; 3989 this.end = start + len; 3990 } 3991 } 3992 3993 Range.prototype = { 3994 length: function() { 3995 return Math.abs(this.end - this.start); 3996 }, 3997 3998 /** 3999 * Returns <code>true</code> if passed range is equals to current one 4000 * @param {Range} range 4001 * @returns {Boolean} 4002 */ 4003 equal: function(range) { 4004 return this.cmp(range, 'eq', 'eq'); 4005 // return this.start === range.start && this.end === range.end; 4006 }, 4007 4008 /** 4009 * Shifts indexes position with passed <code>delat</code> 4010 * @param {Number} delta 4011 * @returns {Range} range itself 4012 */ 4013 shift: function(delta) { 4014 this.start += delta; 4015 this.end += delta; 4016 return this; 4017 }, 4018 4019 /** 4020 * Check if two ranges are overlapped 4021 * @param {Range} range 4022 * @returns {Boolean} 4023 */ 4024 overlap: function(range) { 4025 return range.start <= this.end && range.end >= this.start; 4026 }, 4027 4028 /** 4029 * Finds intersection of two ranges 4030 * @param {Range} range 4031 * @returns {Range} <code>null</code> if ranges does not overlap 4032 */ 4033 intersection: function(range) { 4034 if (this.overlap(range)) { 4035 var start = Math.max(range.start, this.start); 4036 var end = Math.min(range.end, this.end); 4037 return new Range(start, end - start); 4038 } 4039 4040 return null; 4041 }, 4042 4043 /** 4044 * Returns the union of the thow ranges. 4045 * @param {Range} range 4046 * @returns {Range} <code>null</code> if ranges are not overlapped 4047 */ 4048 union: function(range) { 4049 if (this.overlap(range)) { 4050 var start = Math.min(range.start, this.start); 4051 var end = Math.max(range.end, this.end); 4052 return new Range(start, end - start); 4053 } 4054 4055 return null; 4056 }, 4057 4058 /** 4059 * Returns a Boolean value that indicates whether a specified position 4060 * is in a given range. 4061 * @param {Number} loc 4062 */ 4063 inside: function(loc) { 4064 return this.cmp(loc, 'lte', 'gt'); 4065 // return this.start <= loc && this.end > loc; 4066 }, 4067 4068 /** 4069 * Returns a Boolean value that indicates whether a specified position 4070 * is in a given range, but not equals bounds. 4071 * @param {Number} loc 4072 */ 4073 contains: function(loc) { 4074 return this.cmp(loc, 'lt', 'gt'); 4075 }, 4076 4077 /** 4078 * Check if current range completely includes specified one 4079 * @param {Range} r 4080 * @returns {Boolean} 4081 */ 4082 include: function(r) { 4083 return this.cmp(r, 'lte', 'gte'); 4084 // return this.start <= r.start && this.end >= r.end; 4085 }, 4086 4087 /** 4088 * Low-level comparision method 4089 * @param {Number} loc 4090 * @param {String} left Left comparison operator 4091 * @param {String} right Right comaprison operator 4092 */ 4093 cmp: function(loc, left, right) { 4094 var a, b; 4095 if (loc instanceof Range) { 4096 a = loc.start; 4097 b = loc.end; 4098 } else { 4099 a = b = loc; 4100 } 4101 4102 return cmp(this.start, a, left || '<=') && cmp(this.end, b, right || '>'); 4103 }, 4104 4105 /** 4106 * Returns substring of specified <code>str</code> for current range 4107 * @param {String} str 4108 * @returns {String} 4109 */ 4110 substring: function(str) { 4111 return this.length() > 0 4112 ? str.substring(this.start, this.end) 4113 : ''; 4114 }, 4115 4116 /** 4117 * Creates copy of current range 4118 * @returns {Range} 4119 */ 4120 clone: function() { 4121 return new Range(this.start, this.length()); 4122 }, 4123 4124 /** 4125 * @returns {Array} 4126 */ 4127 toArray: function() { 4128 return [this.start, this.end]; 4129 }, 4130 4131 toString: function() { 4132 return this.valueOf(); 4133 }, 4134 4135 valueOf: function() { 4136 return '{' + this.start + ', ' + this.length() + '}'; 4137 } 4138 }; 4139 4140 return { 4141 /** 4142 * Creates new range object instance 4143 * @param {Object} start Range start or array with 'start' and 'end' 4144 * as two first indexes or object with 'start' and 'end' properties 4145 * @param {Number} len Range length or string to produce range from 4146 * @returns {Range} 4147 * @memberOf emmet.range 4148 */ 4149 create: function(start, len) { 4150 if (_.isUndefined(start) || start === null) 4151 return null; 4152 4153 if (start instanceof Range) 4154 return start; 4155 4156 if (_.isObject(start) && 'start' in start && 'end' in start) { 4157 len = start.end - start.start; 4158 start = start.start; 4159 } 4160 4161 return new Range(start, len); 4162 }, 4163 4164 /** 4165 * <code>Range</code> object factory, the same as <code>this.create()</code> 4166 * but last argument represents end of range, not length 4167 * @returns {Range} 4168 */ 4169 create2: function(start, end) { 4170 if (_.isNumber(start) && _.isNumber(end)) { 4171 end -= start; 4172 } 4173 4174 return this.create(start, end); 4175 } 4176 }; 4177 });/** 4178 * Utility module that provides ordered storage of function handlers. 4179 * Many Emmet modules' functionality can be extended/overridden by custom 4180 * function. This modules provides unified storage of handler functions, their 4181 * management and execution 4182 * 4183 * @constructor 4184 * @memberOf __handlerListDefine 4185 * @param {Function} require 4186 * @param {Underscore} _ 4187 */ 4188 emmet.define('handlerList', function(require, _) { 4189 /** 4190 * @type HandlerList 4191 * @constructor 4192 */ 4193 function HandlerList() { 4194 this._list = []; 4195 } 4196 4197 HandlerList.prototype = { 4198 /** 4199 * Adds function handler 4200 * @param {Function} fn Handler 4201 * @param {Object} options Handler options. Possible values are:<br><br> 4202 * <b>order</b> : (<code>Number</code>) – order in handler list. Handlers 4203 * with higher order value will be executed earlier. 4204 */ 4205 add: function(fn, options) { 4206 this._list.push(_.extend({order: 0}, options || {}, {fn: fn})); 4207 }, 4208 4209 /** 4210 * Removes handler from list 4211 * @param {Function} fn 4212 */ 4213 remove: function(fn) { 4214 this._list = _.without(this._list, _.find(this._list, function(item) { 4215 return item.fn === fn; 4216 })); 4217 }, 4218 4219 /** 4220 * Returns ordered list of handlers. By default, handlers 4221 * with the same <code>order</code> option returned in reverse order, 4222 * i.e. the latter function was added into the handlers list, the higher 4223 * it will be in the returned array 4224 * @returns {Array} 4225 */ 4226 list: function() { 4227 return _.sortBy(this._list, 'order').reverse(); 4228 }, 4229 4230 /** 4231 * Returns ordered list of handler functions 4232 * @returns {Array} 4233 */ 4234 listFn: function() { 4235 return _.pluck(this.list(), 'fn'); 4236 }, 4237 4238 /** 4239 * Executes handler functions in their designated order. If function 4240 * returns <code>skipVal</code>, meaning that function was unable to 4241 * handle passed <code>args</code>, the next function will be executed 4242 * and so on. 4243 * @param {Object} skipValue If function returns this value, execute 4244 * next handler. 4245 * @param {Array} args Arguments to pass to handler function 4246 * @returns {Boolean} Whether any of registered handlers performed 4247 * successfully 4248 */ 4249 exec: function(skipValue, args) { 4250 args = args || []; 4251 var result = null; 4252 _.find(this.list(), function(h) { 4253 result = h.fn.apply(h, args); 4254 if (result !== skipValue) 4255 return true; 4256 }); 4257 4258 return result; 4259 } 4260 }; 4261 4262 return { 4263 /** 4264 * Factory method that produces <code>HandlerList</code> instance 4265 * @returns {HandlerList} 4266 * @memberOf handlerList 4267 */ 4268 create: function() { 4269 return new HandlerList(); 4270 } 4271 }; 4272 });/** 4273 * Helper class for convenient token iteration 4274 */ 4275 emmet.define('tokenIterator', function(require, _) { 4276 /** 4277 * @type TokenIterator 4278 * @param {Array} tokens 4279 * @type TokenIterator 4280 * @constructor 4281 */ 4282 function TokenIterator(tokens) { 4283 /** @type Array */ 4284 this.tokens = tokens; 4285 this._position = 0; 4286 this.reset(); 4287 } 4288 4289 TokenIterator.prototype = { 4290 next: function() { 4291 if (this.hasNext()) { 4292 var token = this.tokens[++this._i]; 4293 this._position = token.start; 4294 return token; 4295 } 4296 4297 return null; 4298 }, 4299 4300 current: function() { 4301 return this.tokens[this._i]; 4302 }, 4303 4304 position: function() { 4305 return this._position; 4306 }, 4307 4308 hasNext: function() { 4309 return this._i < this._il - 1; 4310 }, 4311 4312 reset: function() { 4313 this._i = -1; 4314 this._il = this.tokens.length; 4315 }, 4316 4317 item: function() { 4318 return this.tokens[this._i]; 4319 }, 4320 4321 itemNext: function() { 4322 return this.tokens[this._i + 1]; 4323 }, 4324 4325 itemPrev: function() { 4326 return this.tokens[this._i - 1]; 4327 }, 4328 4329 nextUntil: function(type, callback) { 4330 var token; 4331 var test = _.isString(type) 4332 ? function(t){return t.type == type;} 4333 : type; 4334 4335 while ((token = this.next())) { 4336 if (callback) 4337 callback.call(this, token); 4338 if (test.call(this, token)) 4339 break; 4340 } 4341 } 4342 }; 4343 4344 return { 4345 create: function(tokens) { 4346 return new TokenIterator(tokens); 4347 } 4348 }; 4349 });/** 4350 * A trimmed version of CodeMirror's StringStream module for string parsing 4351 */ 4352 emmet.define('stringStream', function(require, _) { 4353 /** 4354 * @type StringStream 4355 * @constructor 4356 * @param {String} string 4357 */ 4358 function StringStream(string) { 4359 this.pos = this.start = 0; 4360 this.string = string; 4361 } 4362 4363 StringStream.prototype = { 4364 /** 4365 * Returns true only if the stream is at the end of the line. 4366 * @returns {Boolean} 4367 */ 4368 eol: function() { 4369 return this.pos >= this.string.length; 4370 }, 4371 4372 /** 4373 * Returns true only if the stream is at the start of the line 4374 * @returns {Boolean} 4375 */ 4376 sol: function() { 4377 return this.pos === 0; 4378 }, 4379 4380 /** 4381 * Returns the next character in the stream without advancing it. 4382 * Will return <code>undefined</code> at the end of the line. 4383 * @returns {String} 4384 */ 4385 peek: function() { 4386 return this.string.charAt(this.pos); 4387 }, 4388 4389 /** 4390 * Returns the next character in the stream and advances it. 4391 * Also returns <code>undefined</code> when no more characters are available. 4392 * @returns {String} 4393 */ 4394 next: function() { 4395 if (this.pos < this.string.length) 4396 return this.string.charAt(this.pos++); 4397 }, 4398 4399 /** 4400 * match can be a character, a regular expression, or a function that 4401 * takes a character and returns a boolean. If the next character in the 4402 * stream 'matches' the given argument, it is consumed and returned. 4403 * Otherwise, undefined is returned. 4404 * @param {Object} match 4405 * @returns {String} 4406 */ 4407 eat: function(match) { 4408 var ch = this.string.charAt(this.pos), ok; 4409 if (typeof match == "string") 4410 ok = ch == match; 4411 else 4412 ok = ch && (match.test ? match.test(ch) : match(ch)); 4413 4414 if (ok) { 4415 ++this.pos; 4416 return ch; 4417 } 4418 }, 4419 4420 /** 4421 * Repeatedly calls <code>eat</code> with the given argument, until it 4422 * fails. Returns <code>true</code> if any characters were eaten. 4423 * @param {Object} match 4424 * @returns {Boolean} 4425 */ 4426 eatWhile: function(match) { 4427 var start = this.pos; 4428 while (this.eat(match)) {} 4429 return this.pos > start; 4430 }, 4431 4432 /** 4433 * Shortcut for <code>eatWhile</code> when matching white-space. 4434 * @returns {Boolean} 4435 */ 4436 eatSpace: function() { 4437 var start = this.pos; 4438 while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) 4439 ++this.pos; 4440 return this.pos > start; 4441 }, 4442 4443 /** 4444 * Moves the position to the end of the line. 4445 */ 4446 skipToEnd: function() { 4447 this.pos = this.string.length; 4448 }, 4449 4450 /** 4451 * Skips to the next occurrence of the given character, if found on the 4452 * current line (doesn't advance the stream if the character does not 4453 * occur on the line). Returns true if the character was found. 4454 * @param {String} ch 4455 * @returns {Boolean} 4456 */ 4457 skipTo: function(ch) { 4458 var found = this.string.indexOf(ch, this.pos); 4459 if (found > -1) { 4460 this.pos = found; 4461 return true; 4462 } 4463 }, 4464 4465 /** 4466 * Skips to <code>close</code> character which is pair to <code>open</code> 4467 * character, considering possible pair nesting. This function is used 4468 * to consume pair of characters, like opening and closing braces 4469 * @param {String} open 4470 * @param {String} close 4471 * @returns {Boolean} Returns <code>true</code> if pair was successfully 4472 * consumed 4473 */ 4474 skipToPair: function(open, close) { 4475 var braceCount = 0, ch; 4476 var pos = this.pos, len = this.string.length; 4477 while (pos < len) { 4478 ch = this.string.charAt(pos++); 4479 if (ch == open) { 4480 braceCount++; 4481 } else if (ch == close) { 4482 braceCount--; 4483 if (braceCount < 1) { 4484 this.pos = pos; 4485 return true; 4486 } 4487 } 4488 } 4489 4490 return false; 4491 }, 4492 4493 /** 4494 * Backs up the stream n characters. Backing it up further than the 4495 * start of the current token will cause things to break, so be careful. 4496 * @param {Number} n 4497 */ 4498 backUp : function(n) { 4499 this.pos -= n; 4500 }, 4501 4502 /** 4503 * Act like a multi-character <code>eat</code>—if <code>consume</code> is true or 4504 * not given—or a look-ahead that doesn't update the stream position—if 4505 * it is false. <code>pattern</code> can be either a string or a 4506 * regular expression starting with ^. When it is a string, 4507 * <code>caseInsensitive</code> can be set to true to make the match 4508 * case-insensitive. When successfully matching a regular expression, 4509 * the returned value will be the array returned by <code>match</code>, 4510 * in case you need to extract matched groups. 4511 * 4512 * @param {RegExp} pattern 4513 * @param {Boolean} consume 4514 * @param {Boolean} caseInsensitive 4515 * @returns 4516 */ 4517 match: function(pattern, consume, caseInsensitive) { 4518 if (typeof pattern == "string") { 4519 var cased = caseInsensitive 4520 ? function(str) {return str.toLowerCase();} 4521 : function(str) {return str;}; 4522 4523 if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { 4524 if (consume !== false) 4525 this.pos += pattern.length; 4526 return true; 4527 } 4528 } else { 4529 var match = this.string.slice(this.pos).match(pattern); 4530 if (match && consume !== false) 4531 this.pos += match[0].length; 4532 return match; 4533 } 4534 }, 4535 4536 /** 4537 * Get the string between the start of the current token and the 4538 * current stream position. 4539 * @returns {String} 4540 */ 4541 current: function() { 4542 return this.string.slice(this.start, this.pos); 4543 } 4544 }; 4545 4546 return { 4547 create: function(string) { 4548 return new StringStream(string); 4549 } 4550 }; 4551 });/** 4552 * Parsed resources (snippets, abbreviations, variables, etc.) for Emmet. 4553 * Contains convenient method to get access for snippets with respect of 4554 * inheritance. Also provides ability to store data in different vocabularies 4555 * ('system' and 'user') for fast and safe resource update 4556 * @author Sergey Chikuyonok (serge.che@gmail.com) 4557 * @link http://chikuyonok.ru 4558 * 4559 * @param {Function} require 4560 * @param {Underscore} _ 4561 */ 4562 emmet.define('resources', function(require, _) { 4563 var VOC_SYSTEM = 'system'; 4564 var VOC_USER = 'user'; 4565 4566 var cache = {}; 4567 4568 /** Regular expression for XML tag matching */ 4569 var reTag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/; 4570 4571 var systemSettings = {}; 4572 var userSettings = {}; 4573 4574 /** @type HandlerList List of registered abbreviation resolvers */ 4575 var resolvers = require('handlerList').create(); 4576 4577 /** 4578 * Normalizes caret plceholder in passed text: replaces | character with 4579 * default caret placeholder 4580 * @param {String} text 4581 * @returns {String} 4582 */ 4583 function normalizeCaretPlaceholder(text) { 4584 var utils = require('utils'); 4585 return utils.replaceUnescapedSymbol(text, '|', utils.getCaretPlaceholder()); 4586 } 4587 4588 function parseItem(name, value, type) { 4589 value = normalizeCaretPlaceholder(value); 4590 4591 if (type == 'snippets') { 4592 return require('elements').create('snippet', value); 4593 } 4594 4595 if (type == 'abbreviations') { 4596 return parseAbbreviation(name, value); 4597 } 4598 } 4599 4600 /** 4601 * Parses single abbreviation 4602 * @param {String} key Abbreviation name 4603 * @param {String} value Abbreviation value 4604 * @return {Object} 4605 */ 4606 function parseAbbreviation(key, value) { 4607 key = require('utils').trim(key); 4608 var elements = require('elements'); 4609 var m; 4610 if ((m = reTag.exec(value))) { 4611 return elements.create('element', m[1], m[2], m[4] == '/'); 4612 } else { 4613 // assume it's reference to another abbreviation 4614 return elements.create('reference', value); 4615 } 4616 } 4617 4618 /** 4619 * Normalizes snippet key name for better fuzzy search 4620 * @param {String} str 4621 * @returns {String} 4622 */ 4623 function normalizeName(str) { 4624 return str.replace(/:$/, '').replace(/:/g, '-'); 4625 } 4626 4627 return { 4628 /** 4629 * Sets new unparsed data for specified settings vocabulary 4630 * @param {Object} data 4631 * @param {String} type Vocabulary type ('system' or 'user') 4632 * @memberOf resources 4633 */ 4634 setVocabulary: function(data, type) { 4635 cache = {}; 4636 if (type == VOC_SYSTEM) 4637 systemSettings = data; 4638 else 4639 userSettings = data; 4640 }, 4641 4642 /** 4643 * Returns resource vocabulary by its name 4644 * @param {String} name Vocabulary name ('system' or 'user') 4645 * @return {Object} 4646 */ 4647 getVocabulary: function(name) { 4648 return name == VOC_SYSTEM ? systemSettings : userSettings; 4649 }, 4650 4651 /** 4652 * Returns resource (abbreviation, snippet, etc.) matched for passed 4653 * abbreviation 4654 * @param {AbbreviationNode} node 4655 * @param {String} syntax 4656 * @returns {Object} 4657 */ 4658 getMatchedResource: function(node, syntax) { 4659 return resolvers.exec(null, _.toArray(arguments)) 4660 || this.findSnippet(syntax, node.name()); 4661 }, 4662 4663 /** 4664 * Returns variable value 4665 * @return {String} 4666 */ 4667 getVariable: function(name) { 4668 return (this.getSection('variables') || {})[name]; 4669 }, 4670 4671 /** 4672 * Store runtime variable in user storage 4673 * @param {String} name Variable name 4674 * @param {String} value Variable value 4675 */ 4676 setVariable: function(name, value){ 4677 var voc = this.getVocabulary('user') || {}; 4678 if (!('variables' in voc)) 4679 voc.variables = {}; 4680 4681 voc.variables[name] = value; 4682 this.setVocabulary(voc, 'user'); 4683 }, 4684 4685 /** 4686 * Check if there are resources for specified syntax 4687 * @param {String} syntax 4688 * @return {Boolean} 4689 */ 4690 hasSyntax: function(syntax) { 4691 return syntax in this.getVocabulary(VOC_USER) 4692 || syntax in this.getVocabulary(VOC_SYSTEM); 4693 }, 4694 4695 /** 4696 * Registers new abbreviation resolver. 4697 * @param {Function} fn Abbreviation resolver which will receive 4698 * abbreviation as first argument and should return parsed abbreviation 4699 * object if abbreviation has handled successfully, <code>null</code> 4700 * otherwise 4701 * @param {Object} options Options list as described in 4702 * {@link HandlerList#add()} method 4703 */ 4704 addResolver: function(fn, options) { 4705 resolvers.add(fn, options); 4706 }, 4707 4708 removeResolver: function(fn) { 4709 resolvers.remove(fn); 4710 }, 4711 4712 /** 4713 * Returns actual section data, merged from both 4714 * system and user data 4715 * @param {String} name Section name (syntax) 4716 * @param {String} ...args Subsections 4717 * @returns 4718 */ 4719 getSection: function(name) { 4720 if (!name) 4721 return null; 4722 4723 if (!(name in cache)) { 4724 cache[name] = require('utils').deepMerge({}, systemSettings[name], userSettings[name]); 4725 } 4726 4727 var data = cache[name], subsections = _.rest(arguments), key; 4728 while (data && (key = subsections.shift())) { 4729 if (key in data) { 4730 data = data[key]; 4731 } else { 4732 return null; 4733 } 4734 } 4735 4736 return data; 4737 }, 4738 4739 /** 4740 * Recursively searches for a item inside top level sections (syntaxes) 4741 * with respect of `extends` attribute 4742 * @param {String} topSection Top section name (syntax) 4743 * @param {String} subsection Inner section name 4744 * @returns {Object} 4745 */ 4746 findItem: function(topSection, subsection) { 4747 var data = this.getSection(topSection); 4748 while (data) { 4749 if (subsection in data) 4750 return data[subsection]; 4751 4752 data = this.getSection(data['extends']); 4753 } 4754 }, 4755 4756 /** 4757 * Recursively searches for a snippet definition inside syntax section. 4758 * Definition is searched inside `snippets` and `abbreviations` 4759 * subsections 4760 * @param {String} syntax Top-level section name (syntax) 4761 * @param {String} name Snippet name 4762 * @returns {Object} 4763 */ 4764 findSnippet: function(syntax, name, memo) { 4765 if (!syntax || !name) 4766 return null; 4767 4768 memo = memo || []; 4769 4770 var names = [name]; 4771 // create automatic aliases to properties with colons, 4772 // e.g. pos-a == pos:a 4773 if (~name.indexOf('-')) 4774 names.push(name.replace(/\-/g, ':')); 4775 4776 var data = this.getSection(syntax), matchedItem = null; 4777 _.find(['snippets', 'abbreviations'], function(sectionName) { 4778 var data = this.getSection(syntax, sectionName); 4779 if (data) { 4780 return _.find(names, function(n) { 4781 if (data[n]) 4782 return matchedItem = parseItem(n, data[n], sectionName); 4783 }); 4784 } 4785 }, this); 4786 4787 memo.push(syntax); 4788 if (!matchedItem && data['extends'] && !_.include(memo, data['extends'])) { 4789 // try to find item in parent syntax section 4790 return this.findSnippet(data['extends'], name, memo); 4791 } 4792 4793 return matchedItem; 4794 }, 4795 4796 /** 4797 * Performs fuzzy search of snippet definition 4798 * @param {String} syntax Top-level section name (syntax) 4799 * @param {String} name Snippet name 4800 * @returns 4801 */ 4802 fuzzyFindSnippet: function(syntax, name, minScore) { 4803 minScore = minScore || 0.3; 4804 4805 var payload = this.getAllSnippets(syntax); 4806 var sc = require('string-score'); 4807 4808 name = normalizeName(name); 4809 var scores = _.map(payload, function(value, key) { 4810 return { 4811 key: key, 4812 score: sc.score(value.nk, name, 0.1) 4813 }; 4814 }); 4815 4816 var result = _.last(_.sortBy(scores, 'score')); 4817 if (result && result.score >= minScore) { 4818 var k = result.key; 4819 return payload[k].parsedValue; 4820 // return parseItem(k, payload[k].value, payload[k].type); 4821 } 4822 }, 4823 4824 /** 4825 * Returns plain dictionary of all available abbreviations and snippets 4826 * for specified syntax with respect of inheritance 4827 * @param {String} syntax 4828 * @returns {Object} 4829 */ 4830 getAllSnippets: function(syntax) { 4831 var cacheKey = 'all-' + syntax; 4832 if (!cache[cacheKey]) { 4833 var stack = [], sectionKey = syntax; 4834 var memo = []; 4835 4836 do { 4837 var section = this.getSection(sectionKey); 4838 if (!section) 4839 break; 4840 4841 _.each(['snippets', 'abbreviations'], function(sectionName) { 4842 var stackItem = {}; 4843 _.each(section[sectionName] || null, function(v, k) { 4844 stackItem[k] = { 4845 nk: normalizeName(k), 4846 value: v, 4847 parsedValue: parseItem(k, v, sectionName), 4848 type: sectionName 4849 }; 4850 }); 4851 4852 stack.push(stackItem); 4853 }); 4854 4855 memo.push(sectionKey); 4856 sectionKey = section['extends']; 4857 } while (sectionKey && !_.include(memo, sectionKey)); 4858 4859 4860 cache[cacheKey] = _.extend.apply(_, stack.reverse()); 4861 } 4862 4863 return cache[cacheKey]; 4864 } 4865 }; 4866 });/** 4867 * Module describes and performs Emmet actions. The actions themselves are 4868 * defined in <i>actions</i> folder 4869 * @param {Function} require 4870 * @param {Underscore} _ 4871 */ 4872 emmet.define('actions', function(require, _) { 4873 var actions = {}; 4874 4875 /** 4876 * “Humanizes” action name, makes it more readable for people 4877 * @param {String} name Action name (like 'expand_abbreviation') 4878 * @return Humanized name (like 'Expand Abbreviation') 4879 */ 4880 function humanizeActionName(name) { 4881 return require('utils').trim(name.charAt(0).toUpperCase() 4882 + name.substring(1).replace(/_[a-z]/g, function(str) { 4883 return ' ' + str.charAt(1).toUpperCase(); 4884 })); 4885 } 4886 4887 return { 4888 /** 4889 * Registers new action 4890 * @param {String} name Action name 4891 * @param {Function} fn Action function 4892 * @param {Object} options Custom action options:<br> 4893 * <b>label</b> : (<code>String</code>) – Human-readable action name. 4894 * May contain '/' symbols as submenu separators<br> 4895 * <b>hidden</b> : (<code>Boolean</code>) – Indicates whether action 4896 * should be displayed in menu (<code>getMenu()</code> method) 4897 * 4898 * @memberOf actions 4899 */ 4900 add: function(name, fn, options) { 4901 name = name.toLowerCase(); 4902 options = options || {}; 4903 if (!options.label) { 4904 options.label = humanizeActionName(name); 4905 } 4906 4907 actions[name] = { 4908 name: name, 4909 fn: fn, 4910 options: options 4911 }; 4912 }, 4913 4914 /** 4915 * Returns action object 4916 * @param {String} name Action name 4917 * @returns {Object} 4918 */ 4919 get: function(name) { 4920 return actions[name.toLowerCase()]; 4921 }, 4922 4923 /** 4924 * Runs Emmet action. For list of available actions and their 4925 * arguments see <i>actions</i> folder. 4926 * @param {String} name Action name 4927 * @param {Array} args Additional arguments. It may be array of arguments 4928 * or inline arguments. The first argument should be <code>IEmmetEditor</code> instance 4929 * @returns {Boolean} Status of performed operation, <code>true</code> 4930 * means action was performed successfully. 4931 * @example 4932 * emmet.require('actions').run('expand_abbreviation', editor); 4933 * emmet.require('actions').run('wrap_with_abbreviation', [editor, 'div']); 4934 */ 4935 run: function(name, args) { 4936 if (!_.isArray(args)) { 4937 args = _.rest(arguments); 4938 } 4939 4940 var action = this.get(name); 4941 if (action) { 4942 return action.fn.apply(emmet, args); 4943 } else { 4944 emmet.log('Action "%s" is not defined', name); 4945 return false; 4946 } 4947 }, 4948 4949 /** 4950 * Returns all registered actions as object 4951 * @returns {Object} 4952 */ 4953 getAll: function() { 4954 return actions; 4955 }, 4956 4957 /** 4958 * Returns all registered actions as array 4959 * @returns {Array} 4960 */ 4961 getList: function() { 4962 return _.values(this.getAll()); 4963 }, 4964 4965 /** 4966 * Returns actions list as structured menu. If action has <i>label</i>, 4967 * it will be splitted by '/' symbol into submenus (for example: 4968 * CSS/Reflect Value) and grouped with other items 4969 * @param {Array} skipActions List of action identifiers that should be 4970 * skipped from menu 4971 * @returns {Array} 4972 */ 4973 getMenu: function(skipActions) { 4974 var result = []; 4975 skipActions = skipActions || []; 4976 _.each(this.getList(), function(action) { 4977 if (action.options.hidden || _.include(skipActions, action.name)) 4978 return; 4979 4980 var actionName = humanizeActionName(action.name); 4981 var ctx = result; 4982 if (action.options.label) { 4983 var parts = action.options.label.split('/'); 4984 actionName = parts.pop(); 4985 4986 // create submenus, if needed 4987 var menuName, submenu; 4988 while ((menuName = parts.shift())) { 4989 submenu = _.find(ctx, function(item) { 4990 return item.type == 'submenu' && item.name == menuName; 4991 }); 4992 4993 if (!submenu) { 4994 submenu = { 4995 name: menuName, 4996 type: 'submenu', 4997 items: [] 4998 }; 4999 ctx.push(submenu); 5000 } 5001 5002 ctx = submenu.items; 5003 } 5004 } 5005 5006 ctx.push({ 5007 type: 'action', 5008 name: action.name, 5009 label: actionName 5010 }); 5011 }); 5012 5013 return result; 5014 }, 5015 5016 /** 5017 * Returns action name associated with menu item title 5018 * @param {String} title 5019 * @returns {String} 5020 */ 5021 getActionNameForMenuTitle: function(title, menu) { 5022 var item = null; 5023 _.find(menu || this.getMenu(), function(val) { 5024 if (val.type == 'action') { 5025 if (val.label == title || val.name == title) { 5026 return item = val.name; 5027 } 5028 } else { 5029 return item = this.getActionNameForMenuTitle(title, val.items); 5030 } 5031 }, this); 5032 5033 return item || null; 5034 } 5035 }; 5036 });/** 5037 * Output profile module. 5038 * Profile defines how XHTML output data should look like 5039 * @param {Function} require 5040 * @param {Underscore} _ 5041 */ 5042 emmet.define('profile', function(require, _) { 5043 var profiles = {}; 5044 5045 var defaultProfile = { 5046 tag_case: 'asis', 5047 attr_case: 'asis', 5048 attr_quotes: 'double', 5049 5050 // each tag on new line 5051 tag_nl: 'decide', 5052 5053 // with tag_nl === true, defines if leaf node (e.g. node with no children) 5054 // should have formatted line breaks 5055 tag_nl_leaf: false, 5056 5057 place_cursor: true, 5058 5059 // indent tags 5060 indent: true, 5061 5062 // how many inline elements should be to force line break 5063 // (set to 0 to disable) 5064 inline_break: 3, 5065 5066 // use self-closing style for writing empty elements, e.g. <br /> or <br> 5067 self_closing_tag: 'xhtml', 5068 5069 // Profile-level output filters, re-defines syntax filters 5070 filters: '', 5071 5072 // Additional filters applied to abbreviation. 5073 // Unlike "filters", this preference doesn't override default filters 5074 // but add the instead every time given profile is chosen 5075 extraFilters: '' 5076 }; 5077 5078 /** 5079 * @constructor 5080 * @type OutputProfile 5081 * @param {Object} options 5082 */ 5083 function OutputProfile(options) { 5084 _.extend(this, defaultProfile, options); 5085 } 5086 5087 OutputProfile.prototype = { 5088 /** 5089 * Transforms tag name case depending on current profile settings 5090 * @param {String} name String to transform 5091 * @returns {String} 5092 */ 5093 tagName: function(name) { 5094 return stringCase(name, this.tag_case); 5095 }, 5096 5097 /** 5098 * Transforms attribute name case depending on current profile settings 5099 * @param {String} name String to transform 5100 * @returns {String} 5101 */ 5102 attributeName: function(name) { 5103 return stringCase(name, this.attr_case); 5104 }, 5105 5106 /** 5107 * Returns quote character for current profile 5108 * @returns {String} 5109 */ 5110 attributeQuote: function() { 5111 return this.attr_quotes == 'single' ? "'" : '"'; 5112 }, 5113 5114 /** 5115 * Returns self-closing tag symbol for current profile 5116 * @returns {String} 5117 */ 5118 selfClosing: function() { 5119 if (this.self_closing_tag == 'xhtml') 5120 return ' /'; 5121 5122 if (this.self_closing_tag === true) 5123 return '/'; 5124 5125 return ''; 5126 }, 5127 5128 /** 5129 * Returns cursor token based on current profile settings 5130 * @returns {String} 5131 */ 5132 cursor: function() { 5133 return this.place_cursor ? require('utils').getCaretPlaceholder() : ''; 5134 } 5135 }; 5136 5137 /** 5138 * Helper function that converts string case depending on 5139 * <code>caseValue</code> 5140 * @param {String} str String to transform 5141 * @param {String} caseValue Case value: can be <i>lower</i>, 5142 * <i>upper</i> and <i>leave</i> 5143 * @returns {String} 5144 */ 5145 function stringCase(str, caseValue) { 5146 switch (String(caseValue || '').toLowerCase()) { 5147 case 'lower': 5148 return str.toLowerCase(); 5149 case 'upper': 5150 return str.toUpperCase(); 5151 } 5152 5153 return str; 5154 } 5155 5156 /** 5157 * Creates new output profile 5158 * @param {String} name Profile name 5159 * @param {Object} options Profile options 5160 */ 5161 function createProfile(name, options) { 5162 return profiles[name.toLowerCase()] = new OutputProfile(options); 5163 } 5164 5165 function createDefaultProfiles() { 5166 createProfile('xhtml'); 5167 createProfile('html', {self_closing_tag: false}); 5168 createProfile('xml', {self_closing_tag: true, tag_nl: true}); 5169 createProfile('plain', {tag_nl: false, indent: false, place_cursor: false}); 5170 createProfile('line', {tag_nl: false, indent: false, extraFilters: 's'}); 5171 } 5172 5173 createDefaultProfiles(); 5174 5175 return { 5176 /** 5177 * Creates new output profile and adds it into internal dictionary 5178 * @param {String} name Profile name 5179 * @param {Object} options Profile options 5180 * @memberOf emmet.profile 5181 * @returns {Object} New profile 5182 */ 5183 create: function(name, options) { 5184 if (arguments.length == 2) 5185 return createProfile(name, options); 5186 else 5187 // create profile object only 5188 return new OutputProfile(_.defaults(name || {}, defaultProfile)); 5189 }, 5190 5191 /** 5192 * Returns profile by its name. If profile wasn't found, returns 5193 * 'plain' profile 5194 * @param {String} name Profile name. Might be profile itself 5195 * @param {String} syntax. Optional. Current editor syntax. If defined, 5196 * profile is searched in resources first, then in predefined profiles 5197 * @returns {Object} 5198 */ 5199 get: function(name, syntax) { 5200 if (!name && syntax) { 5201 // search in user resources first 5202 var profile = require('resources').findItem(syntax, 'profile'); 5203 if (profile) { 5204 name = profile; 5205 } 5206 } 5207 5208 if (!name) { 5209 return profiles.plain; 5210 } 5211 5212 if (name instanceof OutputProfile) { 5213 return name; 5214 } 5215 5216 if (_.isString(name) && name.toLowerCase() in profiles) { 5217 return profiles[name.toLowerCase()]; 5218 } 5219 5220 return this.create(name); 5221 }, 5222 5223 /** 5224 * Deletes profile with specified name 5225 * @param {String} name Profile name 5226 */ 5227 remove: function(name) { 5228 name = (name || '').toLowerCase(); 5229 if (name in profiles) 5230 delete profiles[name]; 5231 }, 5232 5233 /** 5234 * Resets all user-defined profiles 5235 */ 5236 reset: function() { 5237 profiles = {}; 5238 createDefaultProfiles(); 5239 }, 5240 5241 /** 5242 * Helper function that converts string case depending on 5243 * <code>caseValue</code> 5244 * @param {String} str String to transform 5245 * @param {String} caseValue Case value: can be <i>lower</i>, 5246 * <i>upper</i> and <i>leave</i> 5247 * @returns {String} 5248 */ 5249 stringCase: stringCase 5250 }; 5251 });/** 5252 * Utility module used to prepare text for pasting into back-end editor 5253 * @param {Function} require 5254 * @param {Underscore} _ 5255 * @author Sergey Chikuyonok (serge.che@gmail.com) <http://chikuyonok.ru> 5256 */ 5257 emmet.define('editorUtils', function(require, _) { 5258 return { 5259 /** 5260 * Check if cursor is placed inside XHTML tag 5261 * @param {String} html Contents of the document 5262 * @param {Number} caretPos Current caret position inside tag 5263 * @return {Boolean} 5264 */ 5265 isInsideTag: function(html, caretPos) { 5266 var reTag = /^<\/?\w[\w\:\-]*.*?>/; 5267 5268 // search left to find opening brace 5269 var pos = caretPos; 5270 while (pos > -1) { 5271 if (html.charAt(pos) == '<') 5272 break; 5273 pos--; 5274 } 5275 5276 if (pos != -1) { 5277 var m = reTag.exec(html.substring(pos)); 5278 if (m && caretPos > pos && caretPos < pos + m[0].length) 5279 return true; 5280 } 5281 5282 return false; 5283 }, 5284 5285 /** 5286 * Sanitizes incoming editor data and provides default values for 5287 * output-specific info 5288 * @param {IEmmetEditor} editor 5289 * @param {String} syntax 5290 * @param {String} profile 5291 */ 5292 outputInfo: function(editor, syntax, profile) { 5293 // most of this code makes sense for Java/Rhino environment 5294 // because string that comes from Java are not actually JS string 5295 // but Java String object so the have to be explicitly converted 5296 // to native string 5297 profile = profile || editor.getProfileName(); 5298 return { 5299 /** @memberOf outputInfo */ 5300 syntax: String(syntax || editor.getSyntax()), 5301 profile: profile || null, 5302 content: String(editor.getContent()) 5303 }; 5304 }, 5305 5306 /** 5307 * Unindent content, thus preparing text for tag wrapping 5308 * @param {IEmmetEditor} editor Editor instance 5309 * @param {String} text 5310 * @return {String} 5311 */ 5312 unindent: function(editor, text) { 5313 return require('utils').unindentString(text, this.getCurrentLinePadding(editor)); 5314 }, 5315 5316 /** 5317 * Returns padding of current editor's line 5318 * @param {IEmmetEditor} Editor instance 5319 * @return {String} 5320 */ 5321 getCurrentLinePadding: function(editor) { 5322 return require('utils').getLinePadding(editor.getCurrentLine()); 5323 } 5324 }; 5325 }); 5326 /** 5327 * Utility methods for Emmet actions 5328 * @param {Function} require 5329 * @param {Underscore} _ 5330 * @author Sergey Chikuyonok (serge.che@gmail.com) <http://chikuyonok.ru> 5331 */ 5332 emmet.define('actionUtils', function(require, _) { 5333 return { 5334 mimeTypes: { 5335 'gif' : 'image/gif', 5336 'png' : 'image/png', 5337 'jpg' : 'image/jpeg', 5338 'jpeg': 'image/jpeg', 5339 'svg' : 'image/svg+xml', 5340 'html': 'text/html', 5341 'htm' : 'text/html' 5342 }, 5343 5344 /** 5345 * Extracts abbreviations from text stream, starting from the end 5346 * @param {String} str 5347 * @return {String} Abbreviation or empty string 5348 * @memberOf emmet.actionUtils 5349 */ 5350 extractAbbreviation: function(str) { 5351 var curOffset = str.length; 5352 var startIndex = -1; 5353 var groupCount = 0; 5354 var braceCount = 0; 5355 var textCount = 0; 5356 5357 var utils = require('utils'); 5358 var parser = require('abbreviationParser'); 5359 5360 while (true) { 5361 curOffset--; 5362 if (curOffset < 0) { 5363 // moved to the beginning of the line 5364 startIndex = 0; 5365 break; 5366 } 5367 5368 var ch = str.charAt(curOffset); 5369 5370 if (ch == ']') { 5371 braceCount++; 5372 } else if (ch == '[') { 5373 if (!braceCount) { // unexpected brace 5374 startIndex = curOffset + 1; 5375 break; 5376 } 5377 braceCount--; 5378 } else if (ch == '}') { 5379 textCount++; 5380 } else if (ch == '{') { 5381 if (!textCount) { // unexpected brace 5382 startIndex = curOffset + 1; 5383 break; 5384 } 5385 textCount--; 5386 } else if (ch == ')') { 5387 groupCount++; 5388 } else if (ch == '(') { 5389 if (!groupCount) { // unexpected brace 5390 startIndex = curOffset + 1; 5391 break; 5392 } 5393 groupCount--; 5394 } else { 5395 if (braceCount || textCount) 5396 // respect all characters inside attribute sets or text nodes 5397 continue; 5398 else if (!parser.isAllowedChar(ch) || (ch == '>' && utils.endsWithTag(str.substring(0, curOffset + 1)))) { 5399 // found stop symbol 5400 startIndex = curOffset + 1; 5401 break; 5402 } 5403 } 5404 } 5405 5406 if (startIndex != -1 && !textCount && !braceCount && !groupCount) 5407 // found something, remove some invalid symbols from the 5408 // beginning and return abbreviation 5409 return str.substring(startIndex).replace(/^[\*\+\>\^]+/, ''); 5410 else 5411 return ''; 5412 }, 5413 5414 /** 5415 * Gets image size from image byte stream. 5416 * @author http://romeda.org/rePublish/ 5417 * @param {String} stream Image byte stream (use <code>IEmmetFile.read()</code>) 5418 * @return {Object} Object with <code>width</code> and <code>height</code> properties 5419 */ 5420 getImageSize: function(stream) { 5421 var pngMagicNum = "\x89PNG\r\n\x1A\n", 5422 jpgMagicNum = "\xFF\xD8", 5423 gifMagicNum = "GIF8", 5424 pos = 0, 5425 nextByte = function() { 5426 return stream.charCodeAt(pos++); 5427 }; 5428 5429 if (stream.substr(0, 8) === pngMagicNum) { 5430 // PNG. Easy peasy. 5431 pos = stream.indexOf('IHDR') + 4; 5432 5433 return { 5434 width: (nextByte() << 24) | (nextByte() << 16) | (nextByte() << 8) | nextByte(), 5435 height: (nextByte() << 24) | (nextByte() << 16) | (nextByte() << 8) | nextByte() 5436 }; 5437 5438 } else if (stream.substr(0, 4) === gifMagicNum) { 5439 pos = 6; 5440 5441 return { 5442 width: nextByte() | (nextByte() << 8), 5443 height: nextByte() | (nextByte() << 8) 5444 }; 5445 5446 } else if (stream.substr(0, 2) === jpgMagicNum) { 5447 pos = 2; 5448 5449 var l = stream.length; 5450 while (pos < l) { 5451 if (nextByte() != 0xFF) return; 5452 5453 var marker = nextByte(); 5454 if (marker == 0xDA) break; 5455 5456 var size = (nextByte() << 8) | nextByte(); 5457 5458 if (marker >= 0xC0 && marker <= 0xCF && !(marker & 0x4) && !(marker & 0x8)) { 5459 pos += 1; 5460 return { 5461 height: (nextByte() << 8) | nextByte(), 5462 width: (nextByte() << 8) | nextByte() 5463 }; 5464 5465 } else { 5466 pos += size - 2; 5467 } 5468 } 5469 } 5470 }, 5471 5472 /** 5473 * Captures context XHTML element from editor under current caret position. 5474 * This node can be used as a helper for abbreviation extraction 5475 * @param {IEmmetEditor} editor 5476 * @returns {Object} 5477 */ 5478 captureContext: function(editor) { 5479 var allowedSyntaxes = {'html': 1, 'xml': 1, 'xsl': 1}; 5480 var syntax = String(editor.getSyntax()); 5481 if (syntax in allowedSyntaxes) { 5482 var content = String(editor.getContent()); 5483 var tag = require('htmlMatcher').find(content, editor.getCaretPos()); 5484 5485 if (tag && tag.type == 'tag') { 5486 var startTag = tag.open; 5487 var contextNode = { 5488 name: startTag.name, 5489 attributes: [] 5490 }; 5491 5492 // parse attributes 5493 var tagTree = require('xmlEditTree').parse(startTag.range.substring(content)); 5494 if (tagTree) { 5495 contextNode.attributes = _.map(tagTree.getAll(), function(item) { 5496 return { 5497 name: item.name(), 5498 value: item.value() 5499 }; 5500 }); 5501 } 5502 5503 return contextNode; 5504 } 5505 } 5506 5507 return null; 5508 }, 5509 5510 /** 5511 * Find expression bounds in current editor at caret position. 5512 * On each character a <code>fn</code> function will be called and must 5513 * return <code>true</code> if current character meets requirements, 5514 * <code>false</code> otherwise 5515 * @param {IEmmetEditor} editor 5516 * @param {Function} fn Function to test each character of expression 5517 * @return {Range} 5518 */ 5519 findExpressionBounds: function(editor, fn) { 5520 var content = String(editor.getContent()); 5521 var il = content.length; 5522 var exprStart = editor.getCaretPos() - 1; 5523 var exprEnd = exprStart + 1; 5524 5525 // start by searching left 5526 while (exprStart >= 0 && fn(content.charAt(exprStart), exprStart, content)) exprStart--; 5527 5528 // then search right 5529 while (exprEnd < il && fn(content.charAt(exprEnd), exprEnd, content)) exprEnd++; 5530 5531 if (exprEnd > exprStart) { 5532 return require('range').create([++exprStart, exprEnd]); 5533 } 5534 }, 5535 5536 /** 5537 * @param {IEmmetEditor} editor 5538 * @param {Object} data 5539 * @returns {Boolean} 5540 */ 5541 compoundUpdate: function(editor, data) { 5542 if (data) { 5543 var sel = editor.getSelectionRange(); 5544 editor.replaceContent(data.data, data.start, data.end, true); 5545 editor.createSelection(data.caret, data.caret + sel.end - sel.start); 5546 return true; 5547 } 5548 5549 return false; 5550 }, 5551 5552 /** 5553 * Common syntax detection method for editors that doesn’t provide any 5554 * info about current syntax scope. 5555 * @param {IEmmetEditor} editor Current editor 5556 * @param {String} hint Any syntax hint that editor can provide 5557 * for syntax detection. Default is 'html' 5558 * @returns {String} 5559 */ 5560 detectSyntax: function(editor, hint) { 5561 var syntax = hint || 'html'; 5562 5563 if (!require('resources').hasSyntax(syntax)) { 5564 syntax = 'html'; 5565 } 5566 5567 if (syntax == 'html' && (this.isStyle(editor) || this.isInlineCSS(editor))) { 5568 syntax = 'css'; 5569 } 5570 5571 if (syntax == 'styl') { 5572 syntax = 'stylus'; 5573 } 5574 5575 return syntax; 5576 }, 5577 5578 /** 5579 * Common method for detecting output profile 5580 * @param {IEmmetEditor} editor 5581 * @returns {String} 5582 */ 5583 detectProfile: function(editor) { 5584 var syntax = editor.getSyntax(); 5585 5586 // get profile from syntax definition 5587 var profile = require('resources').findItem(syntax, 'profile'); 5588 if (profile) { 5589 return profile; 5590 } 5591 5592 switch(syntax) { 5593 case 'xml': 5594 case 'xsl': 5595 return 'xml'; 5596 case 'css': 5597 if (this.isInlineCSS(editor)) { 5598 return 'line'; 5599 } 5600 break; 5601 case 'html': 5602 profile = require('resources').getVariable('profile'); 5603 if (!profile) { // no forced profile, guess from content 5604 // html or xhtml? 5605 profile = this.isXHTML(editor) ? 'xhtml': 'html'; 5606 } 5607 5608 return profile; 5609 } 5610 5611 return 'xhtml'; 5612 }, 5613 5614 /** 5615 * Tries to detect if current document is XHTML one. 5616 * @param {IEmmetEditor} editor 5617 * @returns {Boolean} 5618 */ 5619 isXHTML: function(editor) { 5620 return editor.getContent().search(/<!DOCTYPE[^>]+XHTML/i) != -1; 5621 }, 5622 5623 /** 5624 * Check if current caret position is inside <style> tag 5625 * @param {IEmmetEditor} editor 5626 * @returns 5627 */ 5628 isStyle: function(editor) { 5629 var content = String(editor.getContent()); 5630 var caretPos = editor.getCaretPos(); 5631 var tag = require('htmlMatcher').tag(content, caretPos); 5632 return tag && tag.open.name.toLowerCase() == 'style' 5633 && tag.innerRange.cmp(caretPos, 'lte', 'gte'); 5634 }, 5635 5636 /** 5637 * Check if current caret position is inside "style" attribute of HTML 5638 * element 5639 * @param {IEmmetEditor} editor 5640 * @returns {Boolean} 5641 */ 5642 isInlineCSS: function(editor) { 5643 var content = String(editor.getContent()); 5644 var caretPos = editor.getCaretPos(); 5645 var tree = require('xmlEditTree').parseFromPosition(content, caretPos, true); 5646 if (tree) { 5647 var attr = tree.itemFromPosition(caretPos, true); 5648 return attr && attr.name().toLowerCase() == 'style' 5649 && attr.valueRange(true).cmp(caretPos, 'lte', 'gte'); 5650 } 5651 5652 return false; 5653 } 5654 }; 5655 });/** 5656 * Utility functions to work with <code>AbbreviationNode</code> as HTML element 5657 * @param {Function} require 5658 * @param {Underscore} _ 5659 */ 5660 emmet.define('abbreviationUtils', function(require, _) { 5661 return { 5662 /** 5663 * Test if passed node is unary (no closing tag) 5664 * @param {AbbreviationNode} node 5665 * @return {Boolean} 5666 */ 5667 isUnary: function(node) { 5668 if (node.children.length || node._text || this.isSnippet(node)) { 5669 return false; 5670 } 5671 5672 var r = node.matchedResource(); 5673 return r && r.is_empty; 5674 }, 5675 5676 /** 5677 * Test if passed node is inline-level (like <strong>, <img>) 5678 * @param {AbbreviationNode} node 5679 * @return {Boolean} 5680 */ 5681 isInline: function(node) { 5682 return node.isTextNode() 5683 || !node.name() 5684 || require('tagName').isInlineLevel(node.name()); 5685 }, 5686 5687 /** 5688 * Test if passed node is block-level 5689 * @param {AbbreviationNode} node 5690 * @return {Boolean} 5691 */ 5692 isBlock: function(node) { 5693 return this.isSnippet(node) || !this.isInline(node); 5694 }, 5695 5696 /** 5697 * Test if given node is a snippet 5698 * @param {AbbreviationNode} node 5699 * @return {Boolean} 5700 */ 5701 isSnippet: function(node) { 5702 return require('elements').is(node.matchedResource(), 'snippet'); 5703 }, 5704 5705 /** 5706 * This function tests if passed node content contains HTML tags. 5707 * This function is mostly used for output formatting 5708 * @param {AbbreviationNode} node 5709 * @returns {Boolean} 5710 */ 5711 hasTagsInContent: function(node) { 5712 return require('utils').matchesTag(node.content); 5713 }, 5714 5715 /** 5716 * Test if current element contains block-level children 5717 * @param {AbbreviationNode} node 5718 * @return {Boolean} 5719 */ 5720 hasBlockChildren: function(node) { 5721 return (this.hasTagsInContent(node) && this.isBlock(node)) 5722 || _.any(node.children, function(child) { 5723 return this.isBlock(child); 5724 }, this); 5725 }, 5726 5727 /** 5728 * Utility function that inserts content instead of <code>${child}</code> 5729 * variables on <code>text</code> 5730 * @param {String} text Text where child content should be inserted 5731 * @param {String} childContent Content to insert 5732 * @param {Object} options 5733 * @returns {String 5734 */ 5735 insertChildContent: function(text, childContent, options) { 5736 options = _.extend({ 5737 keepVariable: true, 5738 appendIfNoChild: true 5739 }, options || {}); 5740 5741 var childVariableReplaced = false; 5742 var utils = require('utils'); 5743 text = utils.replaceVariables(text, function(variable, name, data) { 5744 var output = variable; 5745 if (name == 'child') { 5746 // add correct indentation 5747 output = utils.padString(childContent, utils.getLinePaddingFromPosition(text, data.start)); 5748 childVariableReplaced = true; 5749 if (options.keepVariable) 5750 output += variable; 5751 } 5752 5753 return output; 5754 }); 5755 5756 if (!childVariableReplaced && options.appendIfNoChild) { 5757 text += childContent; 5758 } 5759 5760 return text; 5761 } 5762 }; 5763 });/** 5764 * @author Sergey Chikuyonok (serge.che@gmail.com) 5765 * @link http://chikuyonok.ru 5766 */ 5767 emmet.define('base64', function(require, _) { 5768 var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 5769 5770 return { 5771 /** 5772 * Encodes data using base64 algorithm 5773 * @author Tyler Akins (http://rumkin.com) 5774 * @param {String} input 5775 * @returns {String} 5776 * @memberOf emmet.base64 5777 */ 5778 encode : function(input) { 5779 var output = []; 5780 var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3; 5781 var i = 0, il = input.length, b64 = chars; 5782 5783 while (i < il) { 5784 5785 cdp1 = input.charCodeAt(i++); 5786 cdp2 = input.charCodeAt(i++); 5787 cdp3 = input.charCodeAt(i++); 5788 5789 chr1 = cdp1 & 0xff; 5790 chr2 = cdp2 & 0xff; 5791 chr3 = cdp3 & 0xff; 5792 5793 enc1 = chr1 >> 2; 5794 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 5795 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 5796 enc4 = chr3 & 63; 5797 5798 if (isNaN(cdp2)) { 5799 enc3 = enc4 = 64; 5800 } else if (isNaN(cdp3)) { 5801 enc4 = 64; 5802 } 5803 5804 output.push(b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4)); 5805 } 5806 5807 return output.join(''); 5808 }, 5809 5810 /** 5811 * Decodes string using MIME base64 algorithm 5812 * 5813 * @author Tyler Akins (http://rumkin.com) 5814 * @param {String} data 5815 * @return {String} 5816 */ 5817 decode : function(data) { 5818 var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmpArr = []; 5819 var b64 = chars, il = data.length; 5820 5821 if (!data) { 5822 return data; 5823 } 5824 5825 data += ''; 5826 5827 do { // unpack four hexets into three octets using index points in b64 5828 h1 = b64.indexOf(data.charAt(i++)); 5829 h2 = b64.indexOf(data.charAt(i++)); 5830 h3 = b64.indexOf(data.charAt(i++)); 5831 h4 = b64.indexOf(data.charAt(i++)); 5832 5833 bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; 5834 5835 o1 = bits >> 16 & 0xff; 5836 o2 = bits >> 8 & 0xff; 5837 o3 = bits & 0xff; 5838 5839 if (h3 == 64) { 5840 tmpArr[ac++] = String.fromCharCode(o1); 5841 } else if (h4 == 64) { 5842 tmpArr[ac++] = String.fromCharCode(o1, o2); 5843 } else { 5844 tmpArr[ac++] = String.fromCharCode(o1, o2, o3); 5845 } 5846 } while (i < il); 5847 5848 return tmpArr.join(''); 5849 } 5850 }; 5851 });/** 5852 * HTML matcher: takes string and searches for HTML tag pairs for given position 5853 * 5854 * Unlike “classic” matchers, it parses content from the specified 5855 * position, not from the start, so it may work even outside HTML documents 5856 * (for example, inside strings of programming languages like JavaScript, Python 5857 * etc.) 5858 * @constructor 5859 * @memberOf __htmlMatcherDefine 5860 */ 5861 emmet.define('htmlMatcher', function(require, _) { 5862 // Regular Expressions for parsing tags and attributes 5863 var reOpenTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/; 5864 var reCloseTag = /^<\/([\w\:\-]+)[^>]*>/; 5865 5866 function openTag(i, match) { 5867 return { 5868 name: match[1], 5869 selfClose: !!match[3], 5870 /** @type Range */ 5871 range: require('range').create(i, match[0]), 5872 type: 'open' 5873 }; 5874 } 5875 5876 function closeTag(i, match) { 5877 return { 5878 name: match[1], 5879 /** @type Range */ 5880 range: require('range').create(i, match[0]), 5881 type: 'close' 5882 }; 5883 } 5884 5885 function comment(i, match) { 5886 return { 5887 /** @type Range */ 5888 range: require('range').create(i, _.isNumber(match) ? match - i : match[0]), 5889 type: 'comment' 5890 }; 5891 } 5892 5893 /** 5894 * Creates new tag matcher session 5895 * @param {String} text 5896 */ 5897 function createMatcher(text) { 5898 var memo = {}, m; 5899 return { 5900 /** 5901 * Test if given position matches opening tag 5902 * @param {Number} i 5903 * @returns {Object} Matched tag object 5904 */ 5905 open: function(i) { 5906 var m = this.matches(i); 5907 return m && m.type == 'open' ? m : null; 5908 }, 5909 5910 /** 5911 * Test if given position matches closing tag 5912 * @param {Number} i 5913 * @returns {Object} Matched tag object 5914 */ 5915 close: function(i) { 5916 var m = this.matches(i); 5917 return m && m.type == 'close' ? m : null; 5918 }, 5919 5920 /** 5921 * Matches either opening or closing tag for given position 5922 * @param i 5923 * @returns 5924 */ 5925 matches: function(i) { 5926 var key = 'p' + i; 5927 5928 if (!(key in memo)) { 5929 if (text.charAt(i) == '<') { 5930 var substr = text.slice(i); 5931 if ((m = substr.match(reOpenTag))) { 5932 memo[key] = openTag(i, m); 5933 } else if ((m = substr.match(reCloseTag))) { 5934 memo[key] = closeTag(i, m); 5935 } else { 5936 // remember that given position contains no valid tag 5937 memo[key] = false; 5938 } 5939 } 5940 } 5941 5942 return memo[key]; 5943 }, 5944 5945 /** 5946 * Returns original text 5947 * @returns {String} 5948 */ 5949 text: function() { 5950 return text; 5951 } 5952 }; 5953 } 5954 5955 function matches(text, pos, pattern) { 5956 return text.substring(pos, pos + pattern.length) == pattern; 5957 } 5958 5959 /** 5960 * Search for closing pair of opening tag 5961 * @param {Object} open Open tag instance 5962 * @param {Object} matcher Matcher instance 5963 */ 5964 function findClosingPair(open, matcher) { 5965 var stack = [], tag = null; 5966 var text = matcher.text(); 5967 5968 for (var pos = open.range.end, len = text.length; pos < len; pos++) { 5969 if (matches(text, pos, '<!--')) { 5970 // skip to end of comment 5971 for (var j = pos; j < len; j++) { 5972 if (matches(text, j, '-->')) { 5973 pos = j + 3; 5974 break; 5975 } 5976 } 5977 } 5978 5979 if ((tag = matcher.matches(pos))) { 5980 if (tag.type == 'open' && !tag.selfClose) { 5981 stack.push(tag.name); 5982 } else if (tag.type == 'close') { 5983 if (!stack.length) { // found valid pair? 5984 return tag.name == open.name ? tag : null; 5985 } 5986 5987 // check if current closing tag matches previously opened one 5988 if (_.last(stack) == tag.name) { 5989 stack.pop(); 5990 } else { 5991 var found = false; 5992 while (stack.length && !found) { 5993 var last = stack.pop(); 5994 if (last == tag.name) { 5995 found = true; 5996 } 5997 } 5998 5999 if (!stack.length && !found) { 6000 return tag.name == open.name ? tag : null; 6001 } 6002 } 6003 } 6004 } 6005 6006 } 6007 } 6008 6009 return { 6010 /** 6011 * Main function: search for tag pair in <code>text</code> for given 6012 * position 6013 * @memberOf htmlMatcher 6014 * @param {String} text 6015 * @param {Number} pos 6016 * @returns {Object} 6017 */ 6018 find: function(text, pos) { 6019 var range = require('range'); 6020 var matcher = createMatcher(text); 6021 var open = null, close = null; 6022 var j, jl; 6023 6024 for (var i = pos; i >= 0; i--) { 6025 if ((open = matcher.open(i))) { 6026 // found opening tag 6027 if (open.selfClose) { 6028 if (open.range.cmp(pos, 'lt', 'gt')) { 6029 // inside self-closing tag, found match 6030 break; 6031 } 6032 6033 // outside self-closing tag, continue 6034 continue; 6035 } 6036 6037 close = findClosingPair(open, matcher); 6038 if (close) { 6039 // found closing tag. 6040 var r = range.create2(open.range.start, close.range.end); 6041 if (r.contains(pos)) { 6042 break; 6043 } 6044 } else if (open.range.contains(pos)) { 6045 // we inside empty HTML tag like <br> 6046 break; 6047 } 6048 6049 open = null; 6050 } else if (matches(text, i, '-->')) { 6051 // skip back to comment start 6052 for (j = i - 1; j >= 0; j--) { 6053 if (matches(text, j, '-->')) { 6054 // found another comment end, do nothing 6055 break; 6056 } else if (matches(text, j, '<!--')) { 6057 i = j; 6058 break; 6059 } 6060 } 6061 } else if (matches(text, i, '<!--')) { 6062 // we're inside comment, match it 6063 for (j = i + 4, jl = text.length; j < jl; j++) { 6064 if (matches(text, j, '-->')) { 6065 j += 3; 6066 break; 6067 } 6068 } 6069 6070 open = comment(i, j); 6071 break; 6072 } 6073 } 6074 6075 if (open) { 6076 var outerRange = null; 6077 var innerRange = null; 6078 6079 if (close) { 6080 outerRange = range.create2(open.range.start, close.range.end); 6081 innerRange = range.create2(open.range.end, close.range.start); 6082 } else { 6083 outerRange = innerRange = range.create2(open.range.start, open.range.end); 6084 } 6085 6086 if (open.type == 'comment') { 6087 // adjust positions of inner range for comment 6088 var _c = outerRange.substring(text); 6089 innerRange.start += _c.length - _c.replace(/^<\!--\s*/, '').length; 6090 innerRange.end -= _c.length - _c.replace(/\s*-->$/, '').length; 6091 } 6092 6093 return { 6094 open: open, 6095 close: close, 6096 type: open.type == 'comment' ? 'comment' : 'tag', 6097 innerRange: innerRange, 6098 innerContent: function() { 6099 return this.innerRange.substring(text); 6100 }, 6101 outerRange: outerRange, 6102 outerContent: function() { 6103 return this.outerRange.substring(text); 6104 }, 6105 range: !innerRange.length() || !innerRange.cmp(pos, 'lte', 'gte') ? outerRange : innerRange, 6106 content: function() { 6107 return this.range.substring(text); 6108 }, 6109 source: text 6110 }; 6111 } 6112 }, 6113 6114 /** 6115 * The same as <code>find()</code> method, but restricts matched result 6116 * to <code>tag</code> type 6117 * @param {String} text 6118 * @param {Number} pos 6119 * @returns {Object} 6120 */ 6121 tag: function(text, pos) { 6122 var result = this.find(text, pos); 6123 if (result && result.type == 'tag') { 6124 return result; 6125 } 6126 } 6127 }; 6128 });/** 6129 * Utility module for handling tabstops tokens generated by Emmet's 6130 * "Expand Abbreviation" action. The main <code>extract</code> method will take 6131 * raw text (for example: <i>${0} some ${1:text}</i>), find all tabstops 6132 * occurrences, replace them with tokens suitable for your editor of choice and 6133 * return object with processed text and list of found tabstops and their ranges. 6134 * For sake of portability (Objective-C/Java) the tabstops list is a plain 6135 * sorted array with plain objects. 6136 * 6137 * Placeholders with the same are meant to be <i>linked</i> in your editor. 6138 * @param {Function} require 6139 * @param {Underscore} _ 6140 */ 6141 emmet.define('tabStops', function(require, _) { 6142 /** 6143 * Global placeholder value, automatically incremented by 6144 * <code>variablesResolver()</code> function 6145 */ 6146 var startPlaceholderNum = 100; 6147 6148 var tabstopIndex = 0; 6149 6150 var defaultOptions = { 6151 replaceCarets: false, 6152 escape: function(ch) { 6153 return '\\' + ch; 6154 }, 6155 tabstop: function(data) { 6156 return data.token; 6157 }, 6158 variable: function(data) { 6159 return data.token; 6160 } 6161 }; 6162 6163 // XXX register output processor that will upgrade tabstops of parsed node 6164 // in order to prevent tabstop index conflicts 6165 require('abbreviationParser').addOutputProcessor(function(text, node, type) { 6166 var maxNum = 0; 6167 var tabstops = require('tabStops'); 6168 var utils = require('utils'); 6169 6170 var tsOptions = { 6171 tabstop: function(data) { 6172 var group = parseInt(data.group, 10); 6173 if (group === 0) 6174 return '${0}'; 6175 6176 if (group > maxNum) maxNum = group; 6177 if (data.placeholder) { 6178 // respect nested placeholders 6179 var ix = group + tabstopIndex; 6180 var placeholder = tabstops.processText(data.placeholder, tsOptions); 6181 return '${' + ix + ':' + placeholder + '}'; 6182 } else { 6183 return '${' + (group + tabstopIndex) + '}'; 6184 } 6185 } 6186 }; 6187 6188 // upgrade tabstops 6189 text = tabstops.processText(text, tsOptions); 6190 6191 // resolve variables 6192 text = utils.replaceVariables(text, tabstops.variablesResolver(node)); 6193 6194 tabstopIndex += maxNum + 1; 6195 return text; 6196 }); 6197 6198 return { 6199 /** 6200 * Main function that looks for a tabstops in provided <code>text</code> 6201 * and returns a processed version of <code>text</code> with expanded 6202 * placeholders and list of tabstops found. 6203 * @param {String} text Text to process 6204 * @param {Object} options List of processor options:<br> 6205 * 6206 * <b>replaceCarets</b> : <code>Boolean</code> — replace all default 6207 * caret placeholders (like <i>{%::emmet-caret::%}</i>) with <i>${0:caret}</i><br> 6208 * 6209 * <b>escape</b> : <code>Function</code> — function that handle escaped 6210 * characters (mostly '$'). By default, it returns the character itself 6211 * to be displayed as is in output, but sometimes you will use 6212 * <code>extract</code> method as intermediate solution for further 6213 * processing and want to keep character escaped. Thus, you should override 6214 * <code>escape</code> method to return escaped symbol (e.g. '\\$')<br> 6215 * 6216 * <b>tabstop</b> : <code>Function</code> – a tabstop handler. Receives 6217 * a single argument – an object describing token: its position, number 6218 * group, placeholder and token itself. Should return a replacement 6219 * string that will appear in final output 6220 * 6221 * <b>variable</b> : <code>Function</code> – variable handler. Receives 6222 * a single argument – an object describing token: its position, name 6223 * and original token itself. Should return a replacement 6224 * string that will appear in final output 6225 * 6226 * @returns {Object} Object with processed <code>text</code> property 6227 * and array of <code>tabstops</code> found 6228 * @memberOf tabStops 6229 */ 6230 extract: function(text, options) { 6231 // prepare defaults 6232 var utils = require('utils'); 6233 var placeholders = {carets: ''}; 6234 var marks = []; 6235 6236 options = _.extend({}, defaultOptions, options, { 6237 tabstop: function(data) { 6238 var token = data.token; 6239 var ret = ''; 6240 if (data.placeholder == 'cursor') { 6241 marks.push({ 6242 start: data.start, 6243 end: data.start + token.length, 6244 group: 'carets', 6245 value: '' 6246 }); 6247 } else { 6248 // unify placeholder value for single group 6249 if ('placeholder' in data) 6250 placeholders[data.group] = data.placeholder; 6251 6252 if (data.group in placeholders) 6253 ret = placeholders[data.group]; 6254 6255 marks.push({ 6256 start: data.start, 6257 end: data.start + token.length, 6258 group: data.group, 6259 value: ret 6260 }); 6261 } 6262 6263 return token; 6264 } 6265 }); 6266 6267 if (options.replaceCarets) { 6268 text = text.replace(new RegExp( utils.escapeForRegexp( utils.getCaretPlaceholder() ), 'g'), '${0:cursor}'); 6269 } 6270 6271 // locate tabstops and unify group's placeholders 6272 text = this.processText(text, options); 6273 6274 // now, replace all tabstops with placeholders 6275 var buf = '', lastIx = 0; 6276 var tabStops = _.map(marks, function(mark) { 6277 buf += text.substring(lastIx, mark.start); 6278 6279 var pos = buf.length; 6280 var ph = placeholders[mark.group] || ''; 6281 6282 buf += ph; 6283 lastIx = mark.end; 6284 6285 return { 6286 group: mark.group, 6287 start: pos, 6288 end: pos + ph.length 6289 }; 6290 }); 6291 6292 buf += text.substring(lastIx); 6293 6294 return { 6295 text: buf, 6296 tabstops: _.sortBy(tabStops, 'start') 6297 }; 6298 }, 6299 6300 /** 6301 * Text processing routine. Locates escaped characters and tabstops and 6302 * replaces them with values returned by handlers defined in 6303 * <code>options</code> 6304 * @param {String} text 6305 * @param {Object} options See <code>extract</code> method options 6306 * description 6307 * @returns {String} 6308 */ 6309 processText: function(text, options) { 6310 options = _.extend({}, defaultOptions, options); 6311 6312 var buf = ''; 6313 /** @type StringStream */ 6314 var stream = require('stringStream').create(text); 6315 var ch, m, a; 6316 6317 while ((ch = stream.next())) { 6318 if (ch == '\\' && !stream.eol()) { 6319 // handle escaped character 6320 buf += options.escape(stream.next()); 6321 continue; 6322 } 6323 6324 a = ch; 6325 6326 if (ch == '$') { 6327 // looks like a tabstop 6328 stream.start = stream.pos - 1; 6329 6330 if ((m = stream.match(/^[0-9]+/))) { 6331 // it's $N 6332 a = options.tabstop({ 6333 start: buf.length, 6334 group: stream.current().substr(1), 6335 token: stream.current() 6336 }); 6337 } else if ((m = stream.match(/^\{([a-z_\-][\w\-]*)\}/))) { 6338 // ${variable} 6339 a = options.variable({ 6340 start: buf.length, 6341 name: m[1], 6342 token: stream.current() 6343 }); 6344 } else if ((m = stream.match(/^\{([0-9]+)(:.+?)?\}/, false))) { 6345 // ${N:value} or ${N} placeholder 6346 // parse placeholder, including nested ones 6347 stream.skipToPair('{', '}'); 6348 6349 var obj = { 6350 start: buf.length, 6351 group: m[1], 6352 token: stream.current() 6353 }; 6354 6355 var placeholder = obj.token.substring(obj.group.length + 2, obj.token.length - 1); 6356 6357 if (placeholder) { 6358 obj.placeholder = placeholder.substr(1); 6359 } 6360 6361 a = options.tabstop(obj); 6362 } 6363 } 6364 6365 buf += a; 6366 } 6367 6368 return buf; 6369 }, 6370 6371 /** 6372 * Upgrades tabstops in output node in order to prevent naming conflicts 6373 * @param {AbbreviationNode} node 6374 * @param {Number} offset Tab index offset 6375 * @returns {Number} Maximum tabstop index in element 6376 */ 6377 upgrade: function(node, offset) { 6378 var maxNum = 0; 6379 var options = { 6380 tabstop: function(data) { 6381 var group = parseInt(data.group, 10); 6382 if (group > maxNum) maxNum = group; 6383 6384 if (data.placeholder) 6385 return '${' + (group + offset) + ':' + data.placeholder + '}'; 6386 else 6387 return '${' + (group + offset) + '}'; 6388 } 6389 }; 6390 6391 _.each(['start', 'end', 'content'], function(p) { 6392 node[p] = this.processText(node[p], options); 6393 }, this); 6394 6395 return maxNum; 6396 }, 6397 6398 /** 6399 * Helper function that produces a callback function for 6400 * <code>replaceVariables()</code> method from {@link utils} 6401 * module. This callback will replace variable definitions (like 6402 * ${var_name}) with their value defined in <i>resource</i> module, 6403 * or outputs tabstop with variable name otherwise. 6404 * @param {AbbreviationNode} node Context node 6405 * @returns {Function} 6406 */ 6407 variablesResolver: function(node) { 6408 var placeholderMemo = {}; 6409 var res = require('resources'); 6410 return function(str, varName) { 6411 // do not mark `child` variable as placeholder – it‘s a reserved 6412 // variable name 6413 if (varName == 'child') 6414 return str; 6415 6416 if (varName == 'cursor') 6417 return require('utils').getCaretPlaceholder(); 6418 6419 var attr = node.attribute(varName); 6420 if (!_.isUndefined(attr) && attr !== str) { 6421 return attr; 6422 } 6423 6424 var varValue = res.getVariable(varName); 6425 if (varValue) 6426 return varValue; 6427 6428 // output as placeholder 6429 if (!placeholderMemo[varName]) 6430 placeholderMemo[varName] = startPlaceholderNum++; 6431 6432 return '${' + placeholderMemo[varName] + ':' + varName + '}'; 6433 }; 6434 }, 6435 6436 /** 6437 * Resets global tabstop index. When parsed tree is converted to output 6438 * string (<code>AbbreviationNode.toString()</code>), all tabstops 6439 * defined in snippets and elements are upgraded in order to prevent 6440 * naming conflicts of nested. For example, <code>${1}</code> of a node 6441 * should not be linked with the same placehilder of the child node. 6442 * By default, <code>AbbreviationNode.toString()</code> automatically 6443 * upgrades tabstops of the same index for each node and writes maximum 6444 * tabstop index into the <code>tabstopIndex</code> variable. To keep 6445 * this variable at reasonable value, it is recommended to call 6446 * <code>resetTabstopIndex()</code> method each time you expand variable 6447 * @returns 6448 */ 6449 resetTabstopIndex: function() { 6450 tabstopIndex = 0; 6451 startPlaceholderNum = 100; 6452 } 6453 }; 6454 });/** 6455 * Common module's preferences storage. This module 6456 * provides general storage for all module preferences, their description and 6457 * default values.<br><br> 6458 * 6459 * This module can also be used to list all available properties to create 6460 * UI for updating properties 6461 * 6462 * @memberOf __preferencesDefine 6463 * @constructor 6464 * @param {Function} require 6465 * @param {Underscore} _ 6466 */ 6467 emmet.define('preferences', function(require, _) { 6468 var preferences = {}; 6469 var defaults = {}; 6470 var _dbgDefaults = null; 6471 var _dbgPreferences = null; 6472 6473 function toBoolean(val) { 6474 if (_.isString(val)) { 6475 val = val.toLowerCase(); 6476 return val == 'yes' || val == 'true' || val == '1'; 6477 } 6478 6479 return !!val; 6480 } 6481 6482 function isValueObj(obj) { 6483 return _.isObject(obj) 6484 && 'value' in obj 6485 && _.keys(obj).length < 3; 6486 } 6487 6488 return { 6489 /** 6490 * Creates new preference item with default value 6491 * @param {String} name Preference name. You can also pass object 6492 * with many options 6493 * @param {Object} value Preference default value 6494 * @param {String} description Item textual description 6495 * @memberOf preferences 6496 */ 6497 define: function(name, value, description) { 6498 var prefs = name; 6499 if (_.isString(name)) { 6500 prefs = {}; 6501 prefs[name] = { 6502 value: value, 6503 description: description 6504 }; 6505 } 6506 6507 _.each(prefs, function(v, k) { 6508 defaults[k] = isValueObj(v) ? v : {value: v}; 6509 }); 6510 }, 6511 6512 /** 6513 * Updates preference item value. Preference value should be defined 6514 * first with <code>define</code> method. 6515 * @param {String} name Preference name. You can also pass object 6516 * with many options 6517 * @param {Object} value Preference default value 6518 * @memberOf preferences 6519 */ 6520 set: function(name, value) { 6521 var prefs = name; 6522 if (_.isString(name)) { 6523 prefs = {}; 6524 prefs[name] = value; 6525 } 6526 6527 _.each(prefs, function(v, k) { 6528 if (!(k in defaults)) { 6529 throw 'Property "' + k + '" is not defined. You should define it first with `define` method of current module'; 6530 } 6531 6532 // do not set value if it equals to default value 6533 if (v !== defaults[k].value) { 6534 // make sure we have value of correct type 6535 switch (typeof defaults[k].value) { 6536 case 'boolean': 6537 v = toBoolean(v); 6538 break; 6539 case 'number': 6540 v = parseInt(v + '', 10) || 0; 6541 break; 6542 default: // convert to string 6543 if (v !== null) { 6544 v += ''; 6545 } 6546 } 6547 6548 preferences[k] = v; 6549 } else if (k in preferences) { 6550 delete preferences[k]; 6551 } 6552 }); 6553 }, 6554 6555 /** 6556 * Returns preference value 6557 * @param {String} name 6558 * @returns {String} Returns <code>undefined</code> if preference is 6559 * not defined 6560 */ 6561 get: function(name) { 6562 if (name in preferences) 6563 return preferences[name]; 6564 6565 if (name in defaults) 6566 return defaults[name].value; 6567 6568 return void 0; 6569 }, 6570 6571 /** 6572 * Returns comma-separated preference value as array of values 6573 * @param {String} name 6574 * @returns {Array} Returns <code>undefined</code> if preference is 6575 * not defined, <code>null</code> if string cannot be converted to array 6576 */ 6577 getArray: function(name) { 6578 var val = this.get(name); 6579 if (_.isUndefined(val) || val === null || val === '') { 6580 return null; 6581 } 6582 6583 val = _.map(val.split(','), require('utils').trim); 6584 if (!val.length) { 6585 return null; 6586 } 6587 6588 return val; 6589 }, 6590 6591 /** 6592 * Returns comma and colon-separated preference value as dictionary 6593 * @param {String} name 6594 * @returns {Object} 6595 */ 6596 getDict: function(name) { 6597 var result = {}; 6598 _.each(this.getArray(name), function(val) { 6599 var parts = val.split(':'); 6600 result[parts[0]] = parts[1]; 6601 }); 6602 6603 return result; 6604 }, 6605 6606 /** 6607 * Returns description of preference item 6608 * @param {String} name Preference name 6609 * @returns {Object} 6610 */ 6611 description: function(name) { 6612 return name in defaults ? defaults[name].description : void 0; 6613 }, 6614 6615 /** 6616 * Completely removes specified preference(s) 6617 * @param {String} name Preference name (or array of names) 6618 */ 6619 remove: function(name) { 6620 if (!_.isArray(name)) 6621 name = [name]; 6622 6623 _.each(name, function(key) { 6624 if (key in preferences) 6625 delete preferences[key]; 6626 6627 if (key in defaults) 6628 delete defaults[key]; 6629 }); 6630 }, 6631 6632 /** 6633 * Returns sorted list of all available properties 6634 * @returns {Array} 6635 */ 6636 list: function() { 6637 return _.map(_.keys(defaults).sort(), function(key) { 6638 return { 6639 name: key, 6640 value: this.get(key), 6641 type: typeof defaults[key].value, 6642 description: defaults[key].description 6643 }; 6644 }, this); 6645 }, 6646 6647 /** 6648 * Loads user-defined preferences from JSON 6649 * @param {Object} json 6650 * @returns 6651 */ 6652 load: function(json) { 6653 _.each(json, function(value, key) { 6654 this.set(key, value); 6655 }, this); 6656 }, 6657 6658 /** 6659 * Returns hash of user-modified preferences 6660 * @returns {Object} 6661 */ 6662 exportModified: function() { 6663 return _.clone(preferences); 6664 }, 6665 6666 /** 6667 * Reset to defaults 6668 * @returns 6669 */ 6670 reset: function() { 6671 preferences = {}; 6672 }, 6673 6674 /** 6675 * For unit testing: use empty storage 6676 */ 6677 _startTest: function() { 6678 _dbgDefaults = defaults; 6679 _dbgPreferences = preferences; 6680 defaults = {}; 6681 preferences = {}; 6682 }, 6683 6684 /** 6685 * For unit testing: restore original storage 6686 */ 6687 _stopTest: function() { 6688 defaults = _dbgDefaults; 6689 preferences = _dbgPreferences; 6690 } 6691 }; 6692 });/** 6693 * Module for handling filters 6694 * @param {Function} require 6695 * @param {Underscore} _ 6696 * @author Sergey Chikuyonok (serge.che@gmail.com) <http://chikuyonok.ru> 6697 */ 6698 emmet.define('filters', function(require, _) { 6699 /** List of registered filters */ 6700 var registeredFilters = {}; 6701 6702 /** Filters that will be applied for unknown syntax */ 6703 var basicFilters = 'html'; 6704 6705 function list(filters) { 6706 if (!filters) 6707 return []; 6708 6709 if (_.isString(filters)) 6710 return filters.split(/[\|,]/g); 6711 6712 return filters; 6713 } 6714 6715 return { 6716 /** 6717 * Register new filter 6718 * @param {String} name Filter name 6719 * @param {Function} fn Filter function 6720 */ 6721 add: function(name, fn) { 6722 registeredFilters[name] = fn; 6723 }, 6724 6725 /** 6726 * Apply filters for final output tree 6727 * @param {AbbreviationNode} tree Output tree 6728 * @param {Array} filters List of filters to apply. Might be a 6729 * <code>String</code> 6730 * @param {Object} profile Output profile, defined in <i>profile</i> 6731 * module. Filters defined it profile are not used, <code>profile</code> 6732 * is passed to filter function 6733 * @memberOf emmet.filters 6734 * @returns {AbbreviationNode} 6735 */ 6736 apply: function(tree, filters, profile) { 6737 var utils = require('utils'); 6738 profile = require('profile').get(profile); 6739 6740 _.each(list(filters), function(filter) { 6741 var name = utils.trim(filter.toLowerCase()); 6742 if (name && name in registeredFilters) { 6743 tree = registeredFilters[name](tree, profile); 6744 } 6745 }); 6746 6747 return tree; 6748 }, 6749 6750 /** 6751 * Composes list of filters that should be applied to a tree, based on 6752 * passed data 6753 * @param {String} syntax Syntax name ('html', 'css', etc.) 6754 * @param {Object} profile Output profile 6755 * @param {String} additionalFilters List or pipe-separated 6756 * string of additional filters to apply 6757 * @returns {Array} 6758 */ 6759 composeList: function(syntax, profile, additionalFilters) { 6760 profile = require('profile').get(profile); 6761 var filters = list(profile.filters || require('resources').findItem(syntax, 'filters') || basicFilters); 6762 6763 if (profile.extraFilters) { 6764 filters = filters.concat(list(profile.extraFilters)); 6765 } 6766 6767 if (additionalFilters) { 6768 filters = filters.concat(list(additionalFilters)); 6769 } 6770 6771 if (!filters || !filters.length) { 6772 // looks like unknown syntax, apply basic filters 6773 filters = list(basicFilters); 6774 } 6775 6776 return filters; 6777 }, 6778 6779 /** 6780 * Extracts filter list from abbreviation 6781 * @param {String} abbr 6782 * @returns {Array} Array with cleaned abbreviation and list of 6783 * extracted filters 6784 */ 6785 extractFromAbbreviation: function(abbr) { 6786 var filters = ''; 6787 abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1){ 6788 filters = p1; 6789 return ''; 6790 }); 6791 6792 return [abbr, list(filters)]; 6793 } 6794 }; 6795 });/** 6796 * Module that contains factories for element types used by Emmet 6797 * @param {Function} require 6798 * @param {Underscore} _ 6799 */ 6800 emmet.define('elements', function(require, _) { 6801 var factories = {}; 6802 var reAttrs = /([\w\-:]+)\s*=\s*(['"])(.*?)\2/g; 6803 6804 var result = { 6805 /** 6806 * Create new element factory 6807 * @param {String} name Element identifier 6808 * @param {Function} factory Function that produces element of specified 6809 * type. The object generated by this factory is automatically 6810 * augmented with <code>type</code> property pointing to element 6811 * <code>name</code> 6812 * @memberOf elements 6813 */ 6814 add: function(name, factory) { 6815 var that = this; 6816 factories[name] = function() { 6817 var elem = factory.apply(that, arguments); 6818 if (elem) 6819 elem.type = name; 6820 6821 return elem; 6822 }; 6823 }, 6824 6825 /** 6826 * Returns factory for specified name 6827 * @param {String} name 6828 * @returns {Function} 6829 */ 6830 get: function(name) { 6831 return factories[name]; 6832 }, 6833 6834 /** 6835 * Creates new element with specified type 6836 * @param {String} name 6837 * @returns {Object} 6838 */ 6839 create: function(name) { 6840 var args = [].slice.call(arguments, 1); 6841 var factory = this.get(name); 6842 return factory ? factory.apply(this, args) : null; 6843 }, 6844 6845 /** 6846 * Check if passed element is of specified type 6847 * @param {Object} elem 6848 * @param {String} type 6849 * @returns {Boolean} 6850 */ 6851 is: function(elem, type) { 6852 return elem && elem.type === type; 6853 } 6854 }; 6855 6856 // register resource references 6857 function commonFactory(value) { 6858 return {data: value}; 6859 } 6860 6861 /** 6862 * Element factory 6863 * @param {String} elementName Name of output element 6864 * @param {String} attrs Attributes definition. You may also pass 6865 * <code>Array</code> where each contains object with <code>name</code> 6866 * and <code>value</code> properties, or <code>Object</code> 6867 * @param {Boolean} isEmpty Is expanded element should be empty 6868 */ 6869 result.add('element', function(elementName, attrs, isEmpty) { 6870 var ret = { 6871 /** @memberOf __emmetDataElement */ 6872 name: elementName, 6873 is_empty: !!isEmpty 6874 }; 6875 6876 if (attrs) { 6877 ret.attributes = []; 6878 if (_.isArray(attrs)) { 6879 ret.attributes = attrs; 6880 } else if (_.isString(attrs)) { 6881 var m; 6882 while ((m = reAttrs.exec(attrs))) { 6883 ret.attributes.push({ 6884 name: m[1], 6885 value: m[3] 6886 }); 6887 } 6888 } else { 6889 _.each(attrs, function(value, name) { 6890 ret.attributes.push({ 6891 name: name, 6892 value: value 6893 }); 6894 }); 6895 } 6896 } 6897 6898 return ret; 6899 }); 6900 6901 result.add('snippet', commonFactory); 6902 result.add('reference', commonFactory); 6903 result.add('empty', function() { 6904 return {}; 6905 }); 6906 6907 return result; 6908 });/** 6909 * Abstract implementation of edit tree interface. 6910 * Edit tree is a named container of editable “name-value” child elements, 6911 * parsed from <code>source</code>. This container provides convenient methods 6912 * for editing/adding/removing child elements. All these update actions are 6913 * instantly reflected in the <code>source</code> code with respect of formatting. 6914 * <br><br> 6915 * For example, developer can create an edit tree from CSS rule and add or 6916 * remove properties from it–all changes will be immediately reflected in the 6917 * original source. 6918 * <br><br> 6919 * All classes defined in this module should be extended the same way as in 6920 * Backbone framework: using <code>extend</code> method to create new class and 6921 * <code>initialize</code> method to define custom class constructor. 6922 * 6923 * @example 6924 * <pre><code> 6925 * var MyClass = require('editTree').EditElement.extend({ 6926 * initialize: function() { 6927 * // constructor code here 6928 * } 6929 * }); 6930 * 6931 * var elem = new MyClass(); 6932 * </code></pre> 6933 * 6934 * 6935 * @param {Function} require 6936 * @param {Underscore} _ 6937 * @constructor 6938 * @memberOf __editTreeDefine 6939 */ 6940 emmet.define('editTree', function(require, _, core) { 6941 var range = require('range').create; 6942 6943 /** 6944 * Named container of edited source 6945 * @type EditContainer 6946 * @param {String} source 6947 * @param {Object} options 6948 */ 6949 function EditContainer(source, options) { 6950 this.options = _.extend({offset: 0}, options); 6951 /** 6952 * Source code of edited structure. All changes in the structure are 6953 * immediately reflected into this property 6954 */ 6955 this.source = source; 6956 6957 /** 6958 * List of all editable children 6959 * @private 6960 */ 6961 this._children = []; 6962 6963 /** 6964 * Hash of all positions of container 6965 * @private 6966 */ 6967 this._positions = { 6968 name: 0 6969 }; 6970 6971 this.initialize.apply(this, arguments); 6972 } 6973 6974 /** 6975 * The self-propagating extend function for classes. 6976 * @type Function 6977 */ 6978 EditContainer.extend = core.extend; 6979 6980 EditContainer.prototype = { 6981 /** 6982 * Child class constructor 6983 */ 6984 initialize: function() {}, 6985 6986 /** 6987 * Replace substring of tag's source 6988 * @param {String} value 6989 * @param {Number} start 6990 * @param {Number} end 6991 * @private 6992 */ 6993 _updateSource: function(value, start, end) { 6994 // create modification range 6995 var r = range(start, _.isUndefined(end) ? 0 : end - start); 6996 var delta = value.length - r.length(); 6997 6998 var update = function(obj) { 6999 _.each(obj, function(v, k) { 7000 if (v >= r.end) 7001 obj[k] += delta; 7002 }); 7003 }; 7004 7005 // update affected positions of current container 7006 update(this._positions); 7007 7008 // update affected positions of children 7009 _.each(this.list(), function(item) { 7010 update(item._positions); 7011 }); 7012 7013 this.source = require('utils').replaceSubstring(this.source, value, r); 7014 }, 7015 7016 7017 /** 7018 * Adds new attribute 7019 * @param {String} name Property name 7020 * @param {String} value Property value 7021 * @param {Number} pos Position at which to insert new property. By 7022 * default the property is inserted at the end of rule 7023 * @returns {EditElement} Newly created element 7024 */ 7025 add: function(name, value, pos) { 7026 // this is abstract implementation 7027 var item = new EditElement(name, value); 7028 this._children.push(item); 7029 return item; 7030 }, 7031 7032 /** 7033 * Returns attribute object 7034 * @param {String} name Attribute name or its index 7035 * @returns {EditElement} 7036 */ 7037 get: function(name) { 7038 if (_.isNumber(name)) 7039 return this.list()[name]; 7040 7041 if (_.isString(name)) 7042 return _.find(this.list(), function(prop) { 7043 return prop.name() === name; 7044 }); 7045 7046 return name; 7047 }, 7048 7049 /** 7050 * Returns all children by name or indexes 7051 * @param {Object} name Element name(s) or indexes (<code>String</code>, 7052 * <code>Array</code>, <code>Number</code>) 7053 * @returns {Array} 7054 */ 7055 getAll: function(name) { 7056 if (!_.isArray(name)) 7057 name = [name]; 7058 7059 // split names and indexes 7060 var names = [], indexes = []; 7061 _.each(name, function(item) { 7062 if (_.isString(item)) 7063 names.push(item); 7064 else if (_.isNumber(item)) 7065 indexes.push(item); 7066 }); 7067 7068 return _.filter(this.list(), function(attribute, i) { 7069 return _.include(indexes, i) || _.include(names, attribute.name()); 7070 }); 7071 }, 7072 7073 /** 7074 * Returns or updates element value. If such element doesn't exists, 7075 * it will be created automatically and added at the end of child list. 7076 * @param {String} name Element name or its index 7077 * @param {String} value New element value 7078 * @returns {String} 7079 */ 7080 value: function(name, value, pos) { 7081 var element = this.get(name); 7082 if (element) 7083 return element.value(value); 7084 7085 if (!_.isUndefined(value)) { 7086 // no such element — create it 7087 return this.add(name, value, pos); 7088 } 7089 }, 7090 7091 /** 7092 * Returns all values of child elements found by <code>getAll()</code> 7093 * method 7094 * @param {Object} name Element name(s) or indexes (<code>String</code>, 7095 * <code>Array</code>, <code>Number</code>) 7096 * @returns {Array} 7097 */ 7098 values: function(name) { 7099 return _.map(this.getAll(name), function(element) { 7100 return element.value(); 7101 }); 7102 }, 7103 7104 /** 7105 * Remove child element 7106 * @param {String} name Property name or its index 7107 */ 7108 remove: function(name) { 7109 var element = this.get(name); 7110 if (element) { 7111 this._updateSource('', element.fullRange()); 7112 this._children = _.without(this._children, element); 7113 } 7114 }, 7115 7116 /** 7117 * Returns list of all editable child elements 7118 * @returns {Array} 7119 */ 7120 list: function() { 7121 return this._children; 7122 }, 7123 7124 /** 7125 * Returns index of editble child in list 7126 * @param {Object} item 7127 * @returns {Number} 7128 */ 7129 indexOf: function(item) { 7130 return _.indexOf(this.list(), this.get(item)); 7131 }, 7132 7133 /** 7134 * Sets or gets container name 7135 * @param {String} val New name. If not passed, current 7136 * name is returned 7137 * @return {String} 7138 */ 7139 name: function(val) { 7140 if (!_.isUndefined(val) && this._name !== (val = String(val))) { 7141 this._updateSource(val, this._positions.name, this._positions.name + this._name.length); 7142 this._name = val; 7143 } 7144 7145 return this._name; 7146 }, 7147 7148 /** 7149 * Returns name range object 7150 * @param {Boolean} isAbsolute Return absolute range (with respect of 7151 * rule offset) 7152 * @returns {Range} 7153 */ 7154 nameRange: function(isAbsolute) { 7155 return range(this._positions.name + (isAbsolute ? this.options.offset : 0), this.name()); 7156 }, 7157 7158 /** 7159 * Returns range of current source 7160 * @param {Boolean} isAbsolute 7161 */ 7162 range: function(isAbsolute) { 7163 return range(isAbsolute ? this.options.offset : 0, this.valueOf()); 7164 }, 7165 7166 /** 7167 * Returns element that belongs to specified position 7168 * @param {Number} pos 7169 * @param {Boolean} isAbsolute 7170 * @returns {EditElement} 7171 */ 7172 itemFromPosition: function(pos, isAbsolute) { 7173 return _.find(this.list(), function(elem) { 7174 return elem.range(isAbsolute).inside(pos); 7175 }); 7176 }, 7177 7178 /** 7179 * Returns source code of current container 7180 * @returns {String} 7181 */ 7182 toString: function() { 7183 return this.valueOf(); 7184 }, 7185 7186 valueOf: function() { 7187 return this.source; 7188 } 7189 }; 7190 7191 /** 7192 * @param {EditContainer} parent 7193 * @param {Object} nameToken 7194 * @param {Object} valueToken 7195 */ 7196 function EditElement(parent, nameToken, valueToken) { 7197 /** @type EditContainer */ 7198 this.parent = parent; 7199 7200 this._name = nameToken.value; 7201 this._value = valueToken ? valueToken.value : ''; 7202 7203 this._positions = { 7204 name: nameToken.start, 7205 value: valueToken ? valueToken.start : -1 7206 }; 7207 7208 this.initialize.apply(this, arguments); 7209 } 7210 7211 /** 7212 * The self-propagating extend function for classes. 7213 * @type Function 7214 */ 7215 EditElement.extend = core.extend; 7216 7217 EditElement.prototype = { 7218 /** 7219 * Child class constructor 7220 */ 7221 initialize: function() {}, 7222 7223 /** 7224 * Make position absolute 7225 * @private 7226 * @param {Number} num 7227 * @param {Boolean} isAbsolute 7228 * @returns {Boolean} 7229 */ 7230 _pos: function(num, isAbsolute) { 7231 return num + (isAbsolute ? this.parent.options.offset : 0); 7232 }, 7233 7234 /** 7235 * Sets of gets element value 7236 * @param {String} val New element value. If not passed, current 7237 * value is returned 7238 * @returns {String} 7239 */ 7240 value: function(val) { 7241 if (!_.isUndefined(val) && this._value !== (val = String(val))) { 7242 this.parent._updateSource(val, this.valueRange()); 7243 this._value = val; 7244 } 7245 7246 return this._value; 7247 }, 7248 7249 /** 7250 * Sets of gets element name 7251 * @param {String} val New element name. If not passed, current 7252 * name is returned 7253 * @returns {String} 7254 */ 7255 name: function(val) { 7256 if (!_.isUndefined(val) && this._name !== (val = String(val))) { 7257 this.parent._updateSource(val, this.nameRange()); 7258 this._name = val; 7259 } 7260 7261 return this._name; 7262 }, 7263 7264 /** 7265 * Returns position of element name token 7266 * @param {Boolean} isAbsolute Return absolute position 7267 * @returns {Number} 7268 */ 7269 namePosition: function(isAbsolute) { 7270 return this._pos(this._positions.name, isAbsolute); 7271 }, 7272 7273 /** 7274 * Returns position of element value token 7275 * @param {Boolean} isAbsolute Return absolute position 7276 * @returns {Number} 7277 */ 7278 valuePosition: function(isAbsolute) { 7279 return this._pos(this._positions.value, isAbsolute); 7280 }, 7281 7282 /** 7283 * Returns element name 7284 * @param {Boolean} isAbsolute Return absolute range 7285 * @returns {Range} 7286 */ 7287 range: function(isAbsolute) { 7288 return range(this.namePosition(isAbsolute), this.valueOf()); 7289 }, 7290 7291 /** 7292 * Returns full element range, including possible indentation 7293 * @param {Boolean} isAbsolute Return absolute range 7294 * @returns {Range} 7295 */ 7296 fullRange: function(isAbsolute) { 7297 return this.range(isAbsolute); 7298 }, 7299 7300 /** 7301 * Returns element name range 7302 * @param {Boolean} isAbsolute Return absolute range 7303 * @returns {Range} 7304 */ 7305 nameRange: function(isAbsolute) { 7306 return range(this.namePosition(isAbsolute), this.name()); 7307 }, 7308 7309 /** 7310 * Returns element value range 7311 * @param {Boolean} isAbsolute Return absolute range 7312 * @returns {Range} 7313 */ 7314 valueRange: function(isAbsolute) { 7315 return range(this.valuePosition(isAbsolute), this.value()); 7316 }, 7317 7318 /** 7319 * Returns current element string representation 7320 * @returns {String} 7321 */ 7322 toString: function() { 7323 return this.valueOf(); 7324 }, 7325 7326 valueOf: function() { 7327 return this.name() + this.value(); 7328 } 7329 }; 7330 7331 return { 7332 EditContainer: EditContainer, 7333 EditElement: EditElement, 7334 7335 /** 7336 * Creates token that can be fed to <code>EditElement</code> 7337 * @param {Number} start 7338 * @param {String} value 7339 * @param {String} type 7340 * @returns 7341 */ 7342 createToken: function(start, value, type) { 7343 var obj = { 7344 start: start || 0, 7345 value: value || '', 7346 type: type 7347 }; 7348 7349 obj.end = obj.start + obj.value.length; 7350 return obj; 7351 } 7352 }; 7353 });/** 7354 * CSS EditTree is a module that can parse a CSS rule into a tree with 7355 * convenient methods for adding, modifying and removing CSS properties. These 7356 * changes can be written back to string with respect of code formatting. 7357 * 7358 * @memberOf __cssEditTreeDefine 7359 * @constructor 7360 * @param {Function} require 7361 * @param {Underscore} _ 7362 */ 7363 emmet.define('cssEditTree', function(require, _) { 7364 var defaultOptions = { 7365 styleBefore: '\n\t', 7366 styleSeparator: ': ', 7367 offset: 0 7368 }; 7369 7370 var WHITESPACE_REMOVE_FROM_START = 1; 7371 var WHITESPACE_REMOVE_FROM_END = 2; 7372 7373 /** 7374 * Returns range object 7375 * @param {Number} start 7376 * @param {Number} len 7377 * @returns {Range} 7378 */ 7379 function range(start, len) { 7380 return require('range').create(start, len); 7381 } 7382 7383 /** 7384 * Removes whitespace tokens from the array ends 7385 * @param {Array} tokens 7386 * @param {Number} mask Mask indicating from which end whitespace should be 7387 * removed 7388 * @returns {Array} 7389 */ 7390 function trimWhitespaceTokens(tokens, mask) { 7391 mask = mask || (WHITESPACE_REMOVE_FROM_START | WHITESPACE_REMOVE_FROM_END); 7392 var whitespace = ['white', 'line']; 7393 7394 if ((mask & WHITESPACE_REMOVE_FROM_END) == WHITESPACE_REMOVE_FROM_END) 7395 while (tokens.length && _.include(whitespace, _.last(tokens).type)) { 7396 tokens.pop(); 7397 } 7398 7399 if ((mask & WHITESPACE_REMOVE_FROM_START) == WHITESPACE_REMOVE_FROM_START) 7400 while (tokens.length && _.include(whitespace, tokens[0].type)) { 7401 tokens.shift(); 7402 } 7403 7404 return tokens; 7405 } 7406 7407 /** 7408 * Helper function that searches for selector range for <code>CSSEditRule</code> 7409 * @param {TokenIterator} it 7410 * @returns {Range} 7411 */ 7412 function findSelectorRange(it) { 7413 var tokens = [], token; 7414 var start = it.position(), end; 7415 7416 while ((token = it.next())) { 7417 if (token.type == '{') 7418 break; 7419 tokens.push(token); 7420 } 7421 7422 trimWhitespaceTokens(tokens); 7423 7424 if (tokens.length) { 7425 start = tokens[0].start; 7426 end = _.last(tokens).end; 7427 } else { 7428 end = start; 7429 } 7430 7431 return range(start, end - start); 7432 } 7433 7434 /** 7435 * Helper function that searches for CSS property value range next to 7436 * iterator's current position 7437 * @param {TokenIterator} it 7438 * @returns {Range} 7439 */ 7440 function findValueRange(it) { 7441 // find value start position 7442 var skipTokens = ['white', 'line', ':']; 7443 var tokens = [], token, start, end; 7444 7445 it.nextUntil(function() { 7446 return !_.include(skipTokens, this.itemNext().type); 7447 }); 7448 7449 start = it.current().end; 7450 // consume value 7451 while ((token = it.next())) { 7452 if (token.type == '}' || token.type == ';') { 7453 // found value end 7454 trimWhitespaceTokens(tokens, WHITESPACE_REMOVE_FROM_START 7455 | (token.type == '}' ? WHITESPACE_REMOVE_FROM_END : 0)); 7456 7457 if (tokens.length) { 7458 start = tokens[0].start; 7459 end = _.last(tokens).end; 7460 } else { 7461 end = start; 7462 } 7463 7464 return range(start, end - start); 7465 } 7466 7467 tokens.push(token); 7468 } 7469 7470 // reached the end of tokens list 7471 if (tokens.length) { 7472 return range(tokens[0].start, _.last(tokens).end - tokens[0].start); 7473 } 7474 } 7475 7476 /** 7477 * Finds parts of complex CSS value 7478 * @param {String} str 7479 * @returns {Array} Returns list of <code>Range</code>'s 7480 */ 7481 function findParts(str) { 7482 /** @type StringStream */ 7483 var stream = require('stringStream').create(str); 7484 var ch; 7485 var result = []; 7486 var sep = /[\s\u00a0,]/; 7487 7488 var add = function() { 7489 stream.next(); 7490 result.push(range(stream.start, stream.current())); 7491 stream.start = stream.pos; 7492 }; 7493 7494 // skip whitespace 7495 stream.eatSpace(); 7496 stream.start = stream.pos; 7497 7498 while ((ch = stream.next())) { 7499 if (ch == '"' || ch == "'") { 7500 stream.next(); 7501 if (!stream.skipTo(ch)) break; 7502 add(); 7503 } else if (ch == '(') { 7504 // function found, may have nested function 7505 stream.backUp(1); 7506 if (!stream.skipToPair('(', ')')) break; 7507 stream.backUp(1); 7508 add(); 7509 } else { 7510 if (sep.test(ch)) { 7511 result.push(range(stream.start, stream.current().length - 1)); 7512 stream.eatWhile(sep); 7513 stream.start = stream.pos; 7514 } 7515 } 7516 } 7517 7518 add(); 7519 7520 return _.chain(result) 7521 .filter(function(item) { 7522 return !!item.length(); 7523 }) 7524 .uniq(false, function(item) { 7525 return item.toString(); 7526 }) 7527 .value(); 7528 } 7529 7530 /** 7531 * A bit hacky way to identify invalid CSS property definition: when user 7532 * starts writing new abbreviation in CSS rule, he actually creates invalid 7533 * CSS property definition and this method tries to identify such abbreviation 7534 * and prevent it from being added to CSS edit tree 7535 * @param {TokenIterator} it 7536 */ 7537 function isValidIdentifier(it) { 7538 // return true; 7539 var tokens = it.tokens; 7540 for (var i = it._i + 1, il = tokens.length; i < il; i++) { 7541 if (tokens[i].type == ':') 7542 return true; 7543 7544 if (tokens[i].type == 'identifier' || tokens[i].type == 'line') 7545 return false; 7546 } 7547 7548 return false; 7549 } 7550 7551 /** 7552 * @class 7553 * @extends EditContainer 7554 */ 7555 var CSSEditContainer = require('editTree').EditContainer.extend({ 7556 initialize: function(source, options) { 7557 _.defaults(this.options, defaultOptions); 7558 var editTree = require('editTree'); 7559 7560 /** @type TokenIterator */ 7561 var it = require('tokenIterator').create( 7562 require('cssParser').parse(source)); 7563 7564 var selectorRange = findSelectorRange(it); 7565 this._positions.name = selectorRange.start; 7566 this._name = selectorRange.substring(source); 7567 7568 if (!it.current() || it.current().type != '{') 7569 throw 'Invalid CSS rule'; 7570 7571 this._positions.contentStart = it.position() + 1; 7572 7573 // consume properties 7574 var propertyRange, valueRange, token; 7575 while ((token = it.next())) { 7576 if (token.type == 'identifier' && isValidIdentifier(it)) { 7577 propertyRange = range(token); 7578 valueRange = findValueRange(it); 7579 var end = (it.current() && it.current().type == ';') 7580 ? range(it.current()) 7581 : range(valueRange.end, 0); 7582 this._children.push(new CSSEditElement(this, 7583 editTree.createToken(propertyRange.start, propertyRange.substring(source)), 7584 editTree.createToken(valueRange.start, valueRange.substring(source)), 7585 editTree.createToken(end.start, end.substring(source)) 7586 )); 7587 } 7588 } 7589 7590 this._saveStyle(); 7591 }, 7592 7593 /** 7594 * Remembers all styles of properties 7595 * @private 7596 */ 7597 _saveStyle: function() { 7598 var start = this._positions.contentStart; 7599 var source = this.source; 7600 var utils = require('utils'); 7601 7602 _.each(this.list(), /** @param {CSSEditProperty} p */ function(p) { 7603 p.styleBefore = source.substring(start, p.namePosition()); 7604 // a small hack here: 7605 // Sometimes users add empty lines before properties to logically 7606 // separate groups of properties. In this case, a blind copy of 7607 // characters between rules may lead to undesired behavior, 7608 // especially when current rule is duplicated or used as a donor 7609 // to create new rule. 7610 // To solve this issue, we‘ll take only last newline indentation 7611 var lines = utils.splitByLines(p.styleBefore); 7612 if (lines.length > 1) { 7613 p.styleBefore = '\n' + _.last(lines); 7614 } 7615 7616 p.styleSeparator = source.substring(p.nameRange().end, p.valuePosition()); 7617 7618 // graceful and naive comments removal 7619 p.styleBefore = _.last(p.styleBefore.split('*/')); 7620 p.styleSeparator = p.styleSeparator.replace(/\/\*.*?\*\//g, ''); 7621 7622 start = p.range().end; 7623 }); 7624 }, 7625 7626 /** 7627 * Adds new CSS property 7628 * @param {String} name Property name 7629 * @param {String} value Property value 7630 * @param {Number} pos Position at which to insert new property. By 7631 * default the property is inserted at the end of rule 7632 * @returns {CSSEditProperty} 7633 */ 7634 add: function(name, value, pos) { 7635 var list = this.list(); 7636 var start = this._positions.contentStart; 7637 var styles = _.pick(this.options, 'styleBefore', 'styleSeparator'); 7638 var editTree = require('editTree'); 7639 7640 if (_.isUndefined(pos)) 7641 pos = list.length; 7642 7643 /** @type CSSEditProperty */ 7644 var donor = list[pos]; 7645 if (donor) { 7646 start = donor.fullRange().start; 7647 } else if ((donor = list[pos - 1])) { 7648 // make sure that donor has terminating semicolon 7649 donor.end(';'); 7650 start = donor.range().end; 7651 } 7652 7653 if (donor) { 7654 styles = _.pick(donor, 'styleBefore', 'styleSeparator'); 7655 } 7656 7657 var nameToken = editTree.createToken(start + styles.styleBefore.length, name); 7658 var valueToken = editTree.createToken(nameToken.end + styles.styleSeparator.length, value); 7659 7660 var property = new CSSEditElement(this, nameToken, valueToken, 7661 editTree.createToken(valueToken.end, ';')); 7662 7663 _.extend(property, styles); 7664 7665 // write new property into the source 7666 this._updateSource(property.styleBefore + property.toString(), start); 7667 7668 // insert new property 7669 this._children.splice(pos, 0, property); 7670 return property; 7671 } 7672 }); 7673 7674 /** 7675 * @class 7676 * @type CSSEditElement 7677 * @constructor 7678 */ 7679 var CSSEditElement = require('editTree').EditElement.extend({ 7680 initialize: function(rule, name, value, end) { 7681 this.styleBefore = rule.options.styleBefore; 7682 this.styleSeparator = rule.options.styleSeparator; 7683 7684 this._end = end.value; 7685 this._positions.end = end.start; 7686 }, 7687 7688 /** 7689 * Returns ranges of complex value parts 7690 * @returns {Array} Returns <code>null</code> if value is not complex 7691 */ 7692 valueParts: function(isAbsolute) { 7693 var parts = findParts(this.value()); 7694 if (isAbsolute) { 7695 var offset = this.valuePosition(true); 7696 _.each(parts, function(p) { 7697 p.shift(offset); 7698 }); 7699 } 7700 7701 return parts; 7702 }, 7703 7704 /** 7705 * Sets of gets property end value (basically, it's a semicolon) 7706 * @param {String} val New end value. If not passed, current 7707 * value is returned 7708 */ 7709 end: function(val) { 7710 if (!_.isUndefined(val) && this._end !== val) { 7711 this.parent._updateSource(val, this._positions.end, this._positions.end + this._end.length); 7712 this._end = val; 7713 } 7714 7715 return this._end; 7716 }, 7717 7718 /** 7719 * Returns full rule range, with indentation 7720 * @param {Boolean} isAbsolute Return absolute range (with respect of 7721 * rule offset) 7722 * @returns {Range} 7723 */ 7724 fullRange: function(isAbsolute) { 7725 var r = this.range(isAbsolute); 7726 r.start -= this.styleBefore.length; 7727 return r; 7728 }, 7729 7730 /** 7731 * Returns item string representation 7732 * @returns {String} 7733 */ 7734 valueOf: function() { 7735 return this.name() + this.styleSeparator + this.value() + this.end(); 7736 } 7737 }); 7738 7739 return { 7740 /** 7741 * Parses CSS rule into editable tree 7742 * @param {String} source 7743 * @param {Object} options 7744 * @memberOf emmet.cssEditTree 7745 * @returns {EditContainer} 7746 */ 7747 parse: function(source, options) { 7748 return new CSSEditContainer(source, options); 7749 }, 7750 7751 /** 7752 * Extract and parse CSS rule from specified position in <code>content</code> 7753 * @param {String} content CSS source code 7754 * @param {Number} pos Character position where to start source code extraction 7755 * @returns {EditContainer} 7756 */ 7757 parseFromPosition: function(content, pos, isBackward) { 7758 var bounds = this.extractRule(content, pos, isBackward); 7759 if (!bounds || !bounds.inside(pos)) 7760 // no matching CSS rule or caret outside rule bounds 7761 return null; 7762 7763 return this.parse(bounds.substring(content), { 7764 offset: bounds.start 7765 }); 7766 }, 7767 7768 /** 7769 * Extracts single CSS selector definition from source code 7770 * @param {String} content CSS source code 7771 * @param {Number} pos Character position where to start source code extraction 7772 * @returns {Range} 7773 */ 7774 extractRule: function(content, pos, isBackward) { 7775 var result = ''; 7776 var len = content.length; 7777 var offset = pos; 7778 var stopChars = '{}/\\<>\n\r'; 7779 var bracePos = -1, ch; 7780 7781 // search left until we find rule edge 7782 while (offset >= 0) { 7783 ch = content.charAt(offset); 7784 if (ch == '{') { 7785 bracePos = offset; 7786 break; 7787 } 7788 else if (ch == '}' && !isBackward) { 7789 offset++; 7790 break; 7791 } 7792 7793 offset--; 7794 } 7795 7796 // search right for full rule set 7797 while (offset < len) { 7798 ch = content.charAt(offset); 7799 if (ch == '{') { 7800 bracePos = offset; 7801 } else if (ch == '}') { 7802 if (bracePos != -1) 7803 result = content.substring(bracePos, offset + 1); 7804 break; 7805 } 7806 7807 offset++; 7808 } 7809 7810 if (result) { 7811 // find CSS selector 7812 offset = bracePos - 1; 7813 var selector = ''; 7814 while (offset >= 0) { 7815 ch = content.charAt(offset); 7816 if (stopChars.indexOf(ch) != -1) break; 7817 offset--; 7818 } 7819 7820 // also trim whitespace 7821 selector = content.substring(offset + 1, bracePos).replace(/^[\s\n\r]+/m, ''); 7822 return require('range').create(bracePos - selector.length, result.length + selector.length); 7823 } 7824 7825 return null; 7826 }, 7827 7828 /** 7829 * Removes vendor prefix from CSS property 7830 * @param {String} name CSS property 7831 * @return {String} 7832 */ 7833 baseName: function(name) { 7834 return name.replace(/^\s*\-\w+\-/, ''); 7835 }, 7836 7837 /** 7838 * Finds parts of complex CSS value 7839 * @param {String} str 7840 * @returns {Array} 7841 */ 7842 findParts: findParts 7843 }; 7844 });/** 7845 * XML EditTree is a module that can parse an XML/HTML element into a tree with 7846 * convenient methods for adding, modifying and removing attributes. These 7847 * changes can be written back to string with respect of code formatting. 7848 * 7849 * @memberOf __xmlEditTreeDefine 7850 * @constructor 7851 * @param {Function} require 7852 * @param {Underscore} _ 7853 */ 7854 emmet.define('xmlEditTree', function(require, _) { 7855 var defaultOptions = { 7856 styleBefore: ' ', 7857 styleSeparator: '=', 7858 styleQuote: '"', 7859 offset: 0 7860 }; 7861 7862 var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/m; 7863 7864 var XMLEditContainer = require('editTree').EditContainer.extend({ 7865 initialize: function(source, options) { 7866 _.defaults(this.options, defaultOptions); 7867 this._positions.name = 1; 7868 7869 var attrToken = null; 7870 var tokens = require('xmlParser').parse(source); 7871 var range = require('range'); 7872 7873 _.each(tokens, function(token) { 7874 token.value = range.create(token).substring(source); 7875 switch (token.type) { 7876 case 'tag': 7877 if (/^<[^\/]+/.test(token.value)) { 7878 this._name = token.value.substring(1); 7879 } 7880 break; 7881 7882 case 'attribute': 7883 // add empty attribute 7884 if (attrToken) { 7885 this._children.push(new XMLEditElement(this, attrToken)); 7886 } 7887 7888 attrToken = token; 7889 break; 7890 7891 case 'string': 7892 this._children.push(new XMLEditElement(this, attrToken, token)); 7893 attrToken = null; 7894 break; 7895 } 7896 }, this); 7897 7898 if (attrToken) { 7899 this._children.push(new XMLEditElement(this, attrToken)); 7900 } 7901 7902 this._saveStyle(); 7903 }, 7904 7905 /** 7906 * Remembers all styles of properties 7907 * @private 7908 */ 7909 _saveStyle: function() { 7910 var start = this.nameRange().end; 7911 var source = this.source; 7912 7913 _.each(this.list(), /** @param {EditElement} p */ function(p) { 7914 p.styleBefore = source.substring(start, p.namePosition()); 7915 7916 if (p.valuePosition() !== -1) { 7917 p.styleSeparator = source.substring(p.namePosition() + p.name().length, p.valuePosition() - p.styleQuote.length); 7918 } 7919 7920 start = p.range().end; 7921 }); 7922 }, 7923 7924 /** 7925 * Adds new attribute 7926 * @param {String} name Property name 7927 * @param {String} value Property value 7928 * @param {Number} pos Position at which to insert new property. By 7929 * default the property is inserted at the end of rule 7930 */ 7931 add: function(name, value, pos) { 7932 var list = this.list(); 7933 var start = this.nameRange().end; 7934 var editTree = require('editTree'); 7935 var styles = _.pick(this.options, 'styleBefore', 'styleSeparator', 'styleQuote'); 7936 7937 if (_.isUndefined(pos)) 7938 pos = list.length; 7939 7940 7941 /** @type XMLEditAttribute */ 7942 var donor = list[pos]; 7943 if (donor) { 7944 start = donor.fullRange().start; 7945 } else if ((donor = list[pos - 1])) { 7946 start = donor.range().end; 7947 } 7948 7949 if (donor) { 7950 styles = _.pick(donor, 'styleBefore', 'styleSeparator', 'styleQuote'); 7951 } 7952 7953 value = styles.styleQuote + value + styles.styleQuote; 7954 7955 var attribute = new XMLEditElement(this, 7956 editTree.createToken(start + styles.styleBefore.length, name), 7957 editTree.createToken(start + styles.styleBefore.length + name.length 7958 + styles.styleSeparator.length, value) 7959 ); 7960 7961 _.extend(attribute, styles); 7962 7963 // write new attribute into the source 7964 this._updateSource(attribute.styleBefore + attribute.toString(), start); 7965 7966 // insert new attribute 7967 this._children.splice(pos, 0, attribute); 7968 return attribute; 7969 } 7970 }); 7971 7972 var XMLEditElement = require('editTree').EditElement.extend({ 7973 initialize: function(parent, nameToken, valueToken) { 7974 this.styleBefore = parent.options.styleBefore; 7975 this.styleSeparator = parent.options.styleSeparator; 7976 7977 var value = '', quote = parent.options.styleQuote; 7978 if (valueToken) { 7979 value = valueToken.value; 7980 quote = value.charAt(0); 7981 if (quote == '"' || quote == "'") { 7982 value = value.substring(1); 7983 } else { 7984 quote = ''; 7985 } 7986 7987 if (quote && value.charAt(value.length - 1) == quote) { 7988 value = value.substring(0, value.length - 1); 7989 } 7990 } 7991 7992 this.styleQuote = quote; 7993 7994 this._value = value; 7995 this._positions.value = valueToken ? valueToken.start + quote.length : -1; 7996 }, 7997 7998 /** 7999 * Returns full rule range, with indentation 8000 * @param {Boolean} isAbsolute Return absolute range (with respect of 8001 * rule offset) 8002 * @returns {Range} 8003 */ 8004 fullRange: function(isAbsolute) { 8005 var r = this.range(isAbsolute); 8006 r.start -= this.styleBefore.length; 8007 return r; 8008 }, 8009 8010 valueOf: function() { 8011 return this.name() + this.styleSeparator 8012 + this.styleQuote + this.value() + this.styleQuote; 8013 } 8014 }); 8015 8016 return { 8017 /** 8018 * Parses HTML element into editable tree 8019 * @param {String} source 8020 * @param {Object} options 8021 * @memberOf emmet.htmlEditTree 8022 * @returns {EditContainer} 8023 */ 8024 parse: function(source, options) { 8025 return new XMLEditContainer(source, options); 8026 }, 8027 8028 /** 8029 * Extract and parse HTML from specified position in <code>content</code> 8030 * @param {String} content CSS source code 8031 * @param {Number} pos Character position where to start source code extraction 8032 * @returns {XMLEditElement} 8033 */ 8034 parseFromPosition: function(content, pos, isBackward) { 8035 var bounds = this.extractTag(content, pos, isBackward); 8036 if (!bounds || !bounds.inside(pos)) 8037 // no matching HTML tag or caret outside tag bounds 8038 return null; 8039 8040 return this.parse(bounds.substring(content), { 8041 offset: bounds.start 8042 }); 8043 }, 8044 8045 /** 8046 * Extracts nearest HTML tag range from <code>content</code>, starting at 8047 * <code>pos</code> position 8048 * @param {String} content 8049 * @param {Number} pos 8050 * @param {Boolean} isBackward 8051 * @returns {Range} 8052 */ 8053 extractTag: function(content, pos, isBackward) { 8054 var len = content.length, i; 8055 var range = require('range'); 8056 8057 // max extraction length. I don't think there may be tags larger 8058 // than 2000 characters length 8059 var maxLen = Math.min(2000, len); 8060 8061 /** @type Range */ 8062 var r = null; 8063 8064 var match = function(pos) { 8065 var m; 8066 if (content.charAt(pos) == '<' && (m = content.substr(pos, maxLen).match(startTag))) 8067 return range.create(pos, m[0]); 8068 }; 8069 8070 // lookup backward, in case we are inside tag already 8071 for (i = pos; i >= 0; i--) { 8072 if ((r = match(i))) break; 8073 } 8074 8075 if (r && (r.inside(pos) || isBackward)) 8076 return r; 8077 8078 if (!r && isBackward) 8079 return null; 8080 8081 // search forward 8082 for (i = pos; i < len; i++) { 8083 if ((r = match(i))) 8084 return r; 8085 } 8086 } 8087 }; 8088 });/** 8089 * 'Expand abbreviation' editor action: extracts abbreviation from current caret 8090 * position and replaces it with formatted output. 8091 * <br><br> 8092 * This behavior can be overridden with custom handlers which can perform 8093 * different actions when 'Expand Abbreviation' action is called. 8094 * For example, a CSS gradient handler that produces vendor-prefixed gradient 8095 * definitions registers its own expand abbreviation handler. 8096 * 8097 * @constructor 8098 * @memberOf __expandAbbreviationActionDefine 8099 * @param {Function} require 8100 * @param {Underscore} _ 8101 */ 8102 emmet.define('expandAbbreviation', function(require, _) { 8103 /** 8104 * @type HandlerList List of registered handlers 8105 */ 8106 var handlers = require('handlerList').create(); 8107 8108 /** Back-reference to module */ 8109 var module = null; 8110 8111 var actions = require('actions'); 8112 /** 8113 * 'Expand abbreviation' editor action 8114 * @param {IEmmetEditor} editor Editor instance 8115 * @param {String} syntax Syntax type (html, css, etc.) 8116 * @param {String} profile Output profile name (html, xml, xhtml) 8117 * @return {Boolean} Returns <code>true</code> if abbreviation was expanded 8118 * successfully 8119 */ 8120 actions.add('expand_abbreviation', function(editor, syntax, profile) { 8121 var args = _.toArray(arguments); 8122 8123 // normalize incoming arguments 8124 var info = require('editorUtils').outputInfo(editor, syntax, profile); 8125 args[1] = info.syntax; 8126 args[2] = info.profile; 8127 8128 return handlers.exec(false, args); 8129 }); 8130 8131 /** 8132 * A special version of <code>expandAbbreviation</code> function: if it can't 8133 * find abbreviation, it will place Tab character at caret position 8134 * @param {IEmmetEditor} editor Editor instance 8135 * @param {String} syntax Syntax type (html, css, etc.) 8136 * @param {String} profile Output profile name (html, xml, xhtml) 8137 */ 8138 actions.add('expand_abbreviation_with_tab', function(editor, syntax, profile) { 8139 var sel = editor.getSelection(); 8140 var indent = require('resources').getVariable('indentation'); 8141 if (sel) { 8142 // indent selection 8143 var utils = require('utils'); 8144 var selRange = require('range').create(editor.getSelectionRange()); 8145 var content = utils.padString(sel, indent); 8146 8147 editor.replaceContent(indent + '${0}', editor.getCaretPos()); 8148 var replaceRange = require('range').create(editor.getCaretPos(), selRange.length()); 8149 editor.replaceContent(content, replaceRange.start, replaceRange.end, true); 8150 editor.createSelection(replaceRange.start, replaceRange.start + content.length); 8151 return true; 8152 } 8153 8154 if (!actions.run('expand_abbreviation', editor, syntax, profile)) { 8155 editor.replaceContent(indent, editor.getCaretPos()); 8156 } 8157 8158 return true; 8159 }, {hidden: true}); 8160 8161 // XXX setup default handler 8162 /** 8163 * Extracts abbreviation from current caret 8164 * position and replaces it with formatted output 8165 * @param {IEmmetEditor} editor Editor instance 8166 * @param {String} syntax Syntax type (html, css, etc.) 8167 * @param {String} profile Output profile name (html, xml, xhtml) 8168 * @return {Boolean} Returns <code>true</code> if abbreviation was expanded 8169 * successfully 8170 */ 8171 handlers.add(function(editor, syntax, profile) { 8172 var caretPos = editor.getSelectionRange().end; 8173 var abbr = module.findAbbreviation(editor); 8174 8175 if (abbr) { 8176 var content = emmet.expandAbbreviation(abbr, syntax, profile, 8177 require('actionUtils').captureContext(editor)); 8178 if (content) { 8179 editor.replaceContent(content, caretPos - abbr.length, caretPos); 8180 return true; 8181 } 8182 } 8183 8184 return false; 8185 }, {order: -1}); 8186 8187 return module = { 8188 /** 8189 * Adds custom expand abbreviation handler. The passed function should 8190 * return <code>true</code> if it was performed successfully, 8191 * <code>false</code> otherwise. 8192 * 8193 * Added handlers will be called when 'Expand Abbreviation' is called 8194 * in order they were added 8195 * @memberOf expandAbbreviation 8196 * @param {Function} fn 8197 * @param {Object} options 8198 */ 8199 addHandler: function(fn, options) { 8200 handlers.add(fn, options); 8201 }, 8202 8203 /** 8204 * Removes registered handler 8205 * @returns 8206 */ 8207 removeHandler: function(fn) { 8208 handlers.remove(fn); 8209 }, 8210 8211 /** 8212 * Search for abbreviation in editor from current caret position 8213 * @param {IEmmetEditor} editor Editor instance 8214 * @return {String} 8215 */ 8216 findAbbreviation: function(editor) { 8217 /** @type Range */ 8218 var range = require('range').create(editor.getSelectionRange()); 8219 var content = String(editor.getContent()); 8220 if (range.length()) { 8221 // abbreviation is selected by user 8222 return range.substring(content); 8223 } 8224 8225 // search for new abbreviation from current caret position 8226 var curLine = editor.getCurrentLineRange(); 8227 return require('actionUtils').extractAbbreviation(content.substring(curLine.start, range.start)); 8228 } 8229 }; 8230 });/** 8231 * Action that wraps content with abbreviation. For convenience, action is 8232 * defined as reusable module 8233 * @constructor 8234 * @memberOf __wrapWithAbbreviationDefine 8235 */ 8236 emmet.define('wrapWithAbbreviation', function(require, _) { 8237 /** Back-references to current module */ 8238 var module = null; 8239 8240 /** 8241 * Wraps content with abbreviation 8242 * @param {IEmmetEditor} Editor instance 8243 * @param {String} abbr Abbreviation to wrap with 8244 * @param {String} syntax Syntax type (html, css, etc.) 8245 * @param {String} profile Output profile name (html, xml, xhtml) 8246 */ 8247 require('actions').add('wrap_with_abbreviation', function (editor, abbr, syntax, profile) { 8248 var info = require('editorUtils').outputInfo(editor, syntax, profile); 8249 var utils = require('utils'); 8250 /** @type emmet.editorUtils */ 8251 var editorUtils = require('editorUtils'); 8252 abbr = abbr || editor.prompt("Enter abbreviation"); 8253 8254 if (!abbr) 8255 return null; 8256 8257 abbr = String(abbr); 8258 8259 var range = require('range').create(editor.getSelectionRange()); 8260 8261 if (!range.length()) { 8262 // no selection, find tag pair 8263 var match = require('htmlMatcher').tag(info.content, range.start); 8264 if (!match) { // nothing to wrap 8265 return false; 8266 } 8267 8268 range = utils.narrowToNonSpace(info.content, match.range); 8269 } 8270 8271 var newContent = utils.escapeText(range.substring(info.content)); 8272 var result = module 8273 .wrap(abbr, editorUtils.unindent(editor, newContent), info.syntax, 8274 info.profile, require('actionUtils').captureContext(editor)); 8275 8276 if (result) { 8277 editor.replaceContent(result, range.start, range.end); 8278 return true; 8279 } 8280 8281 return false; 8282 }); 8283 8284 return module = { 8285 /** 8286 * Wraps passed text with abbreviation. Text will be placed inside last 8287 * expanded element 8288 * @memberOf wrapWithAbbreviation 8289 * @param {String} abbr Abbreviation 8290 * @param {String} text Text to wrap 8291 * @param {String} syntax Document type (html, xml, etc.). Default is 'html' 8292 * @param {String} profile Output profile's name. Default is 'plain' 8293 * @param {Object} contextNode Context node inside which abbreviation 8294 * is wrapped. It will be used as a reference for node name resolvers 8295 * @return {String} 8296 */ 8297 wrap: function(abbr, text, syntax, profile, contextNode) { 8298 /** @type emmet.filters */ 8299 var filters = require('filters'); 8300 /** @type emmet.utils */ 8301 var utils = require('utils'); 8302 8303 syntax = syntax || emmet.defaultSyntax(); 8304 profile = require('profile').get(profile, syntax); 8305 8306 require('tabStops').resetTabstopIndex(); 8307 8308 var data = filters.extractFromAbbreviation(abbr); 8309 var parsedTree = require('abbreviationParser').parse(data[0], { 8310 syntax: syntax, 8311 pastedContent: text, 8312 contextNode: contextNode 8313 }); 8314 if (parsedTree) { 8315 var filtersList = filters.composeList(syntax, profile, data[1]); 8316 filters.apply(parsedTree, filtersList, profile); 8317 return utils.replaceVariables(parsedTree.valueOf()); 8318 } 8319 8320 return null; 8321 } 8322 }; 8323 });/** 8324 * Toggles HTML and CSS comments depending on current caret context. Unlike 8325 * the same action in most editors, this action toggles comment on currently 8326 * matched item—HTML tag or CSS selector—when nothing is selected. 8327 * 8328 * @param {Function} require 8329 * @param {Underscore} _ 8330 * @memberOf __toggleCommentAction 8331 * @constructor 8332 */ 8333 emmet.exec(function(require, _) { 8334 var prefs = require('preferences'); 8335 8336 /** 8337 * Toggle HTML comment on current selection or tag 8338 * @param {IEmmetEditor} editor 8339 * @return {Boolean} Returns <code>true</code> if comment was toggled 8340 */ 8341 function toggleHTMLComment(editor) { 8342 /** @type Range */ 8343 var range = require('range').create(editor.getSelectionRange()); 8344 var info = require('editorUtils').outputInfo(editor); 8345 8346 if (!range.length()) { 8347 // no selection, find matching tag 8348 var tag = require('htmlMatcher').tag(info.content, editor.getCaretPos()); 8349 if (tag) { // found pair 8350 range = tag.outerRange; 8351 } 8352 } 8353 8354 return genericCommentToggle(editor, '<!--', '-->', range); 8355 } 8356 8357 /** 8358 * Simple CSS commenting 8359 * @param {IEmmetEditor} editor 8360 * @return {Boolean} Returns <code>true</code> if comment was toggled 8361 */ 8362 function toggleCSSComment(editor) { 8363 /** @type Range */ 8364 var range = require('range').create(editor.getSelectionRange()); 8365 var info = require('editorUtils').outputInfo(editor); 8366 8367 if (!range.length()) { 8368 // no selection, try to get current rule 8369 /** @type CSSRule */ 8370 var rule = require('cssEditTree').parseFromPosition(info.content, editor.getCaretPos()); 8371 if (rule) { 8372 var property = cssItemFromPosition(rule, editor.getCaretPos()); 8373 range = property 8374 ? property.range(true) 8375 : require('range').create(rule.nameRange(true).start, rule.source); 8376 } 8377 } 8378 8379 if (!range.length()) { 8380 // still no selection, get current line 8381 range = require('range').create(editor.getCurrentLineRange()); 8382 require('utils').narrowToNonSpace(info.content, range); 8383 } 8384 8385 return genericCommentToggle(editor, '/*', '*/', range); 8386 } 8387 8388 /** 8389 * Returns CSS property from <code>rule</code> that matches passed position 8390 * @param {EditContainer} rule 8391 * @param {Number} absPos 8392 * @returns {EditElement} 8393 */ 8394 function cssItemFromPosition(rule, absPos) { 8395 // do not use default EditContainer.itemFromPosition() here, because 8396 // we need to make a few assumptions to make CSS commenting more reliable 8397 var relPos = absPos - (rule.options.offset || 0); 8398 var reSafeChar = /^[\s\n\r]/; 8399 return _.find(rule.list(), function(item) { 8400 if (item.range().end === relPos) { 8401 // at the end of property, but outside of it 8402 // if there’s a space character at current position, 8403 // use current property 8404 return reSafeChar.test(rule.source.charAt(relPos)); 8405 } 8406 8407 return item.range().inside(relPos); 8408 }); 8409 } 8410 8411 /** 8412 * Search for nearest comment in <code>str</code>, starting from index <code>from</code> 8413 * @param {String} text Where to search 8414 * @param {Number} from Search start index 8415 * @param {String} start_token Comment start string 8416 * @param {String} end_token Comment end string 8417 * @return {Range} Returns null if comment wasn't found 8418 */ 8419 function searchComment(text, from, startToken, endToken) { 8420 var commentStart = -1; 8421 var commentEnd = -1; 8422 8423 var hasMatch = function(str, start) { 8424 return text.substr(start, str.length) == str; 8425 }; 8426 8427 // search for comment start 8428 while (from--) { 8429 if (hasMatch(startToken, from)) { 8430 commentStart = from; 8431 break; 8432 } 8433 } 8434 8435 if (commentStart != -1) { 8436 // search for comment end 8437 from = commentStart; 8438 var contentLen = text.length; 8439 while (contentLen >= from++) { 8440 if (hasMatch(endToken, from)) { 8441 commentEnd = from + endToken.length; 8442 break; 8443 } 8444 } 8445 } 8446 8447 return (commentStart != -1 && commentEnd != -1) 8448 ? require('range').create(commentStart, commentEnd - commentStart) 8449 : null; 8450 } 8451 8452 /** 8453 * Generic comment toggling routine 8454 * @param {IEmmetEditor} editor 8455 * @param {String} commentStart Comment start token 8456 * @param {String} commentEnd Comment end token 8457 * @param {Range} range Selection range 8458 * @return {Boolean} 8459 */ 8460 function genericCommentToggle(editor, commentStart, commentEnd, range) { 8461 var editorUtils = require('editorUtils'); 8462 var content = editorUtils.outputInfo(editor).content; 8463 var caretPos = editor.getCaretPos(); 8464 var newContent = null; 8465 8466 var utils = require('utils'); 8467 8468 /** 8469 * Remove comment markers from string 8470 * @param {Sting} str 8471 * @return {String} 8472 */ 8473 function removeComment(str) { 8474 return str 8475 .replace(new RegExp('^' + utils.escapeForRegexp(commentStart) + '\\s*'), function(str){ 8476 caretPos -= str.length; 8477 return ''; 8478 }).replace(new RegExp('\\s*' + utils.escapeForRegexp(commentEnd) + '$'), ''); 8479 } 8480 8481 // first, we need to make sure that this substring is not inside 8482 // comment 8483 var commentRange = searchComment(content, caretPos, commentStart, commentEnd); 8484 if (commentRange && commentRange.overlap(range)) { 8485 // we're inside comment, remove it 8486 range = commentRange; 8487 newContent = removeComment(range.substring(content)); 8488 } else { 8489 // should add comment 8490 // make sure that there's no comment inside selection 8491 newContent = commentStart + ' ' + 8492 range.substring(content) 8493 .replace(new RegExp(utils.escapeForRegexp(commentStart) + '\\s*|\\s*' + utils.escapeForRegexp(commentEnd), 'g'), '') + 8494 ' ' + commentEnd; 8495 8496 // adjust caret position 8497 caretPos += commentStart.length + 1; 8498 } 8499 8500 // replace editor content 8501 if (newContent !== null) { 8502 newContent = utils.escapeText(newContent); 8503 editor.setCaretPos(range.start); 8504 editor.replaceContent(editorUtils.unindent(editor, newContent), range.start, range.end); 8505 editor.setCaretPos(caretPos); 8506 return true; 8507 } 8508 8509 return false; 8510 } 8511 8512 /** 8513 * Toggle comment on current editor's selection or HTML tag/CSS rule 8514 * @param {IEmmetEditor} editor 8515 */ 8516 require('actions').add('toggle_comment', function(editor) { 8517 var info = require('editorUtils').outputInfo(editor); 8518 if (info.syntax == 'css') { 8519 // in case our editor is good enough and can recognize syntax from 8520 // current token, we have to make sure that cursor is not inside 8521 // 'style' attribute of html element 8522 var caretPos = editor.getCaretPos(); 8523 var tag = require('htmlMatcher').tag(info.content, caretPos); 8524 if (tag && tag.open.range.inside(caretPos)) { 8525 info.syntax = 'html'; 8526 } 8527 } 8528 8529 var cssSyntaxes = prefs.getArray('css.syntaxes'); 8530 if (_.include(cssSyntaxes, info.syntax)) { 8531 return toggleCSSComment(editor); 8532 } 8533 8534 return toggleHTMLComment(editor); 8535 }); 8536 });/** 8537 * Move between next/prev edit points. 'Edit points' are places between tags 8538 * and quotes of empty attributes in html 8539 * @constructor 8540 * 8541 * @memberOf __editPointActionDefine 8542 * @param {Function} require 8543 * @param {Underscore} _ 8544 */ 8545 emmet.exec(function(require, _) { 8546 /** 8547 * Search for new caret insertion point 8548 * @param {IEmmetEditor} editor Editor instance 8549 * @param {Number} inc Search increment: -1 — search left, 1 — search right 8550 * @param {Number} offset Initial offset relative to current caret position 8551 * @return {Number} Returns -1 if insertion point wasn't found 8552 */ 8553 function findNewEditPoint(editor, inc, offset) { 8554 inc = inc || 1; 8555 offset = offset || 0; 8556 8557 var curPoint = editor.getCaretPos() + offset; 8558 var content = String(editor.getContent()); 8559 var maxLen = content.length; 8560 var nextPoint = -1; 8561 var reEmptyLine = /^\s+$/; 8562 8563 function getLine(ix) { 8564 var start = ix; 8565 while (start >= 0) { 8566 var c = content.charAt(start); 8567 if (c == '\n' || c == '\r') 8568 break; 8569 start--; 8570 } 8571 8572 return content.substring(start, ix); 8573 } 8574 8575 while (curPoint <= maxLen && curPoint >= 0) { 8576 curPoint += inc; 8577 var curChar = content.charAt(curPoint); 8578 var nextChar = content.charAt(curPoint + 1); 8579 var prevChar = content.charAt(curPoint - 1); 8580 8581 switch (curChar) { 8582 case '"': 8583 case '\'': 8584 if (nextChar == curChar && prevChar == '=') { 8585 // empty attribute 8586 nextPoint = curPoint + 1; 8587 } 8588 break; 8589 case '>': 8590 if (nextChar == '<') { 8591 // between tags 8592 nextPoint = curPoint + 1; 8593 } 8594 break; 8595 case '\n': 8596 case '\r': 8597 // empty line 8598 if (reEmptyLine.test(getLine(curPoint - 1))) { 8599 nextPoint = curPoint; 8600 } 8601 break; 8602 } 8603 8604 if (nextPoint != -1) 8605 break; 8606 } 8607 8608 return nextPoint; 8609 } 8610 8611 /** @type emmet.actions */ 8612 var actions = require('actions'); 8613 8614 /** 8615 * Move caret to previous edit point 8616 * @param {IEmmetEditor} editor Editor instance 8617 */ 8618 actions.add('prev_edit_point', function(editor) { 8619 var curPos = editor.getCaretPos(); 8620 var newPoint = findNewEditPoint(editor, -1); 8621 8622 if (newPoint == curPos) 8623 // we're still in the same point, try searching from the other place 8624 newPoint = findNewEditPoint(editor, -1, -2); 8625 8626 if (newPoint != -1) { 8627 editor.setCaretPos(newPoint); 8628 return true; 8629 } 8630 8631 return false; 8632 }, {label: 'Previous Edit Point'}); 8633 8634 /** 8635 * Move caret to next edit point 8636 * @param {IEmmetEditor} editor Editor instance 8637 */ 8638 actions.add('next_edit_point', function(editor) { 8639 var newPoint = findNewEditPoint(editor, 1); 8640 if (newPoint != -1) { 8641 editor.setCaretPos(newPoint); 8642 return true; 8643 } 8644 8645 return false; 8646 }); 8647 });/** 8648 * Actions that use stream parsers and tokenizers for traversing: 8649 * -- Search for next/previous items in HTML 8650 * -- Search for next/previous items in CSS 8651 * @constructor 8652 * @memberOf __selectItemActionDefine 8653 * @param {Function} require 8654 * @param {Underscore} _ 8655 */ 8656 emmet.exec(function(require, _) { 8657 var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/; 8658 8659 /** 8660 * Generic function for searching for items to select 8661 * @param {IEmmetEditor} editor 8662 * @param {Boolean} isBackward Search backward (search forward otherwise) 8663 * @param {Function} extractFn Function that extracts item content 8664 * @param {Function} rangeFn Function that search for next token range 8665 */ 8666 function findItem(editor, isBackward, extractFn, rangeFn) { 8667 var range = require('range'); 8668 var content = require('editorUtils').outputInfo(editor).content; 8669 8670 var contentLength = content.length; 8671 var itemRange, rng; 8672 /** @type Range */ 8673 var prevRange = range.create(-1, 0); 8674 /** @type Range */ 8675 var sel = range.create(editor.getSelectionRange()); 8676 8677 var searchPos = sel.start, loop = 100000; // endless loop protection 8678 while (searchPos >= 0 && searchPos < contentLength && --loop > 0) { 8679 if ( (itemRange = extractFn(content, searchPos, isBackward)) ) { 8680 if (prevRange.equal(itemRange)) { 8681 break; 8682 } 8683 8684 prevRange = itemRange.clone(); 8685 rng = rangeFn(itemRange.substring(content), itemRange.start, sel.clone()); 8686 8687 if (rng) { 8688 editor.createSelection(rng.start, rng.end); 8689 return true; 8690 } else { 8691 searchPos = isBackward ? itemRange.start : itemRange.end - 1; 8692 } 8693 } 8694 8695 searchPos += isBackward ? -1 : 1; 8696 } 8697 8698 return false; 8699 } 8700 8701 // XXX HTML section 8702 8703 /** 8704 * Find next HTML item 8705 * @param {IEmmetEditor} editor 8706 */ 8707 function findNextHTMLItem(editor) { 8708 var isFirst = true; 8709 return findItem(editor, false, function(content, searchPos){ 8710 if (isFirst) { 8711 isFirst = false; 8712 return findOpeningTagFromPosition(content, searchPos); 8713 } else { 8714 return getOpeningTagFromPosition(content, searchPos); 8715 } 8716 }, function(tag, offset, selRange) { 8717 return getRangeForHTMLItem(tag, offset, selRange, false); 8718 }); 8719 } 8720 8721 /** 8722 * Find previous HTML item 8723 * @param {IEmmetEditor} editor 8724 */ 8725 function findPrevHTMLItem(editor) { 8726 return findItem(editor, true, getOpeningTagFromPosition, function (tag, offset, selRange) { 8727 return getRangeForHTMLItem(tag, offset, selRange, true); 8728 }); 8729 } 8730 8731 /** 8732 * Creates possible selection ranges for HTML tag 8733 * @param {String} source Original HTML source for tokens 8734 * @param {Array} tokens List of HTML tokens 8735 * @returns {Array} 8736 */ 8737 function makePossibleRangesHTML(source, tokens, offset) { 8738 offset = offset || 0; 8739 var range = require('range'); 8740 var result = []; 8741 var attrStart = -1, attrName = '', attrValue = '', attrValueRange, tagName; 8742 _.each(tokens, function(tok) { 8743 switch (tok.type) { 8744 case 'tag': 8745 tagName = source.substring(tok.start, tok.end); 8746 if (/^<[\w\:\-]/.test(tagName)) { 8747 // add tag name 8748 result.push(range.create({ 8749 start: tok.start + 1, 8750 end: tok.end 8751 })); 8752 } 8753 break; 8754 case 'attribute': 8755 attrStart = tok.start; 8756 attrName = source.substring(tok.start, tok.end); 8757 break; 8758 8759 case 'string': 8760 // attribute value 8761 // push full attribute first 8762 result.push(range.create(attrStart, tok.end - attrStart)); 8763 8764 attrValueRange = range.create(tok); 8765 attrValue = attrValueRange.substring(source); 8766 8767 // is this a quoted attribute? 8768 if (isQuote(attrValue.charAt(0))) 8769 attrValueRange.start++; 8770 8771 if (isQuote(attrValue.charAt(attrValue.length - 1))) 8772 attrValueRange.end--; 8773 8774 result.push(attrValueRange); 8775 8776 if (attrName == 'class') { 8777 result = result.concat(classNameRanges(attrValueRange.substring(source), attrValueRange.start)); 8778 } 8779 8780 break; 8781 } 8782 }); 8783 8784 // offset ranges 8785 _.each(result, function(r) { 8786 r.shift(offset); 8787 }); 8788 8789 return _.chain(result) 8790 .filter(function(item) { // remove empty 8791 return !!item.length(); 8792 }) 8793 .uniq(false, function(item) { // remove duplicates 8794 return item.toString(); 8795 }) 8796 .value(); 8797 } 8798 8799 /** 8800 * Returns ranges of class names in "class" attribute value 8801 * @param {String} className 8802 * @returns {Array} 8803 */ 8804 function classNameRanges(className, offset) { 8805 offset = offset || 0; 8806 var result = []; 8807 /** @type StringStream */ 8808 var stream = require('stringStream').create(className); 8809 var range = require('range'); 8810 8811 // skip whitespace 8812 stream.eatSpace(); 8813 stream.start = stream.pos; 8814 8815 var ch; 8816 while ((ch = stream.next())) { 8817 if (/[\s\u00a0]/.test(ch)) { 8818 result.push(range.create(stream.start + offset, stream.pos - stream.start - 1)); 8819 stream.eatSpace(); 8820 stream.start = stream.pos; 8821 } 8822 } 8823 8824 result.push(range.create(stream.start + offset, stream.pos - stream.start)); 8825 return result; 8826 } 8827 8828 /** 8829 * Returns best HTML tag range match for current selection 8830 * @param {String} tag Tag declaration 8831 * @param {Number} offset Tag's position index inside content 8832 * @param {Range} selRange Selection range 8833 * @return {Range} Returns range if next item was found, <code>null</code> otherwise 8834 */ 8835 function getRangeForHTMLItem(tag, offset, selRange, isBackward) { 8836 var ranges = makePossibleRangesHTML(tag, require('xmlParser').parse(tag), offset); 8837 8838 if (isBackward) 8839 ranges.reverse(); 8840 8841 // try to find selected range 8842 var curRange = _.find(ranges, function(r) { 8843 return r.equal(selRange); 8844 }); 8845 8846 if (curRange) { 8847 var ix = _.indexOf(ranges, curRange); 8848 if (ix < ranges.length - 1) 8849 return ranges[ix + 1]; 8850 8851 return null; 8852 } 8853 8854 // no selected range, find nearest one 8855 if (isBackward) 8856 // search backward 8857 return _.find(ranges, function(r) { 8858 return r.start < selRange.start; 8859 }); 8860 8861 // search forward 8862 // to deal with overlapping ranges (like full attribute definition 8863 // and attribute value) let's find range under caret first 8864 if (!curRange) { 8865 var matchedRanges = _.filter(ranges, function(r) { 8866 return r.inside(selRange.end); 8867 }); 8868 8869 if (matchedRanges.length > 1) 8870 return matchedRanges[1]; 8871 } 8872 8873 8874 return _.find(ranges, function(r) { 8875 return r.end > selRange.end; 8876 }); 8877 } 8878 8879 /** 8880 * Search for opening tag in content, starting at specified position 8881 * @param {String} html Where to search tag 8882 * @param {Number} pos Character index where to start searching 8883 * @return {Range} Returns range if valid opening tag was found, 8884 * <code>null</code> otherwise 8885 */ 8886 function findOpeningTagFromPosition(html, pos) { 8887 var tag; 8888 while (pos >= 0) { 8889 if ((tag = getOpeningTagFromPosition(html, pos))) 8890 return tag; 8891 pos--; 8892 } 8893 8894 return null; 8895 } 8896 8897 /** 8898 * @param {String} html Where to search tag 8899 * @param {Number} pos Character index where to start searching 8900 * @return {Range} Returns range if valid opening tag was found, 8901 * <code>null</code> otherwise 8902 */ 8903 function getOpeningTagFromPosition(html, pos) { 8904 var m; 8905 if (html.charAt(pos) == '<' && (m = html.substring(pos, html.length).match(startTag))) { 8906 return require('range').create(pos, m[0]); 8907 } 8908 } 8909 8910 function isQuote(ch) { 8911 return ch == '"' || ch == "'"; 8912 } 8913 8914 /** 8915 * Makes all possible selection ranges for specified CSS property 8916 * @param {CSSProperty} property 8917 * @returns {Array} 8918 */ 8919 function makePossibleRangesCSS(property) { 8920 // find all possible ranges, sorted by position and size 8921 var valueRange = property.valueRange(true); 8922 var result = [property.range(true), valueRange]; 8923 var stringStream = require('stringStream'); 8924 var cssEditTree = require('cssEditTree'); 8925 var range = require('range'); 8926 8927 // locate parts of complex values. 8928 // some examples: 8929 // – 1px solid red: 3 parts 8930 // – arial, sans-serif: enumeration, 2 parts 8931 // – url(image.png): function value part 8932 var value = property.value(); 8933 _.each(property.valueParts(), function(r) { 8934 // add absolute range 8935 var clone = r.clone(); 8936 result.push(clone.shift(valueRange.start)); 8937 8938 /** @type StringStream */ 8939 var stream = stringStream.create(r.substring(value)); 8940 if (stream.match(/^[\w\-]+\(/, true)) { 8941 // we have a function, find values in it. 8942 // but first add function contents 8943 stream.start = stream.pos; 8944 stream.skipToPair('(', ')'); 8945 var fnBody = stream.current(); 8946 result.push(range.create(clone.start + stream.start, fnBody)); 8947 8948 // find parts 8949 _.each(cssEditTree.findParts(fnBody), function(part) { 8950 result.push(range.create(clone.start + stream.start + part.start, part.substring(fnBody))); 8951 }); 8952 } 8953 }); 8954 8955 // optimize result: remove empty ranges and duplicates 8956 return _.chain(result) 8957 .filter(function(item) { 8958 return !!item.length(); 8959 }) 8960 .uniq(false, function(item) { 8961 return item.toString(); 8962 }) 8963 .value(); 8964 } 8965 8966 /** 8967 * Tries to find matched CSS property and nearest range for selection 8968 * @param {CSSRule} rule 8969 * @param {Range} selRange 8970 * @param {Boolean} isBackward 8971 * @returns {Range} 8972 */ 8973 function matchedRangeForCSSProperty(rule, selRange, isBackward) { 8974 /** @type CSSProperty */ 8975 var property = null; 8976 var possibleRanges, curRange = null, ix; 8977 var list = rule.list(); 8978 var searchFn, nearestItemFn; 8979 8980 if (isBackward) { 8981 list.reverse(); 8982 searchFn = function(p) { 8983 return p.range(true).start <= selRange.start; 8984 }; 8985 nearestItemFn = function(r) { 8986 return r.start < selRange.start; 8987 }; 8988 } else { 8989 searchFn = function(p) { 8990 return p.range(true).end >= selRange.end; 8991 }; 8992 nearestItemFn = function(r) { 8993 return r.end > selRange.start; 8994 }; 8995 } 8996 8997 // search for nearest to selection CSS property 8998 var eqSel = function(r) { 8999 return r.equal(selRange); 9000 }; 9001 var inSel = function(r) { 9002 return r.inside(selRange.end); 9003 }; 9004 while ((property = _.find(list, searchFn))) { 9005 possibleRanges = makePossibleRangesCSS(property); 9006 if (isBackward) 9007 possibleRanges.reverse(); 9008 9009 // check if any possible range is already selected 9010 curRange = _.find(possibleRanges, eqSel); 9011 9012 if (!curRange) { 9013 // no selection, select nearest item 9014 var matchedRanges = _.filter(possibleRanges, inSel); 9015 9016 if (matchedRanges.length > 1) { 9017 curRange = matchedRanges[1]; 9018 break; 9019 } 9020 9021 if ((curRange = _.find(possibleRanges, nearestItemFn))) 9022 break; 9023 } else { 9024 ix = _.indexOf(possibleRanges, curRange); 9025 if (ix != possibleRanges.length - 1) { 9026 curRange = possibleRanges[ix + 1]; 9027 break; 9028 } 9029 } 9030 9031 curRange = null; 9032 selRange.start = selRange.end = isBackward 9033 ? property.range(true).start - 1 9034 : property.range(true).end + 1; 9035 } 9036 9037 return curRange; 9038 } 9039 9040 function findNextCSSItem(editor) { 9041 return findItem(editor, false, require('cssEditTree').extractRule, getRangeForNextItemInCSS); 9042 } 9043 9044 function findPrevCSSItem(editor) { 9045 return findItem(editor, true, require('cssEditTree').extractRule, getRangeForPrevItemInCSS); 9046 } 9047 9048 /** 9049 * Returns range for item to be selected in CSS after current caret 9050 * (selection) position 9051 * @param {String} rule CSS rule declaration 9052 * @param {Number} offset Rule's position index inside content 9053 * @param {Range} selRange Selection range 9054 * @return {Range} Returns range if next item was found, <code>null</code> otherwise 9055 */ 9056 function getRangeForNextItemInCSS(rule, offset, selRange) { 9057 var tree = require('cssEditTree').parse(rule, { 9058 offset: offset 9059 }); 9060 9061 // check if selector is matched 9062 var range = tree.nameRange(true); 9063 if (selRange.end < range.end) { 9064 return range; 9065 } 9066 9067 return matchedRangeForCSSProperty(tree, selRange, false); 9068 } 9069 9070 /** 9071 * Returns range for item to be selected in CSS before current caret 9072 * (selection) position 9073 * @param {String} rule CSS rule declaration 9074 * @param {Number} offset Rule's position index inside content 9075 * @param {Range} selRange Selection range 9076 * @return {Range} Returns range if previous item was found, <code>null</code> otherwise 9077 */ 9078 function getRangeForPrevItemInCSS(rule, offset, selRange) { 9079 var tree = require('cssEditTree').parse(rule, { 9080 offset: offset 9081 }); 9082 9083 var curRange = matchedRangeForCSSProperty(tree, selRange, true); 9084 9085 if (!curRange) { 9086 // no matched property, try to match selector 9087 var range = tree.nameRange(true); 9088 if (selRange.start > range.start) { 9089 return range; 9090 } 9091 } 9092 9093 return curRange; 9094 } 9095 9096 // XXX register actions 9097 var actions = require('actions'); 9098 actions.add('select_next_item', function(editor){ 9099 if (editor.getSyntax() == 'css') 9100 return findNextCSSItem(editor); 9101 else 9102 return findNextHTMLItem(editor); 9103 }); 9104 9105 actions.add('select_previous_item', function(editor){ 9106 if (editor.getSyntax() == 'css') 9107 return findPrevCSSItem(editor); 9108 else 9109 return findPrevHTMLItem(editor); 9110 }); 9111 });/** 9112 * HTML pair matching (balancing) actions 9113 * @constructor 9114 * @memberOf __matchPairActionDefine 9115 * @param {Function} require 9116 * @param {Underscore} _ 9117 */ 9118 emmet.exec(function(require, _) { 9119 /** @type emmet.actions */ 9120 var actions = require('actions'); 9121 var matcher = require('htmlMatcher'); 9122 var lastMatch = null; 9123 9124 /** 9125 * Find and select HTML tag pair 9126 * @param {IEmmetEditor} editor Editor instance 9127 * @param {String} direction Direction of pair matching: 'in' or 'out'. 9128 * Default is 'out' 9129 */ 9130 function matchPair(editor, direction) { 9131 direction = String((direction || 'out').toLowerCase()); 9132 var info = require('editorUtils').outputInfo(editor); 9133 9134 var range = require('range'); 9135 /** @type Range */ 9136 var sel = range.create(editor.getSelectionRange()); 9137 var content = info.content; 9138 9139 // validate previous match 9140 if (lastMatch && !lastMatch.range.equal(sel)) { 9141 lastMatch = null; 9142 } 9143 9144 if (lastMatch && sel.length()) { 9145 if (direction == 'in') { 9146 // user has previously selected tag and wants to move inward 9147 if (lastMatch.type == 'tag' && !lastMatch.close) { 9148 // unary tag was selected, can't move inward 9149 return false; 9150 } else { 9151 if (lastMatch.range.equal(lastMatch.outerRange)) { 9152 lastMatch.range = lastMatch.innerRange; 9153 } else { 9154 var narrowed = require('utils').narrowToNonSpace(content, lastMatch.innerRange); 9155 lastMatch = matcher.find(content, narrowed.start + 1); 9156 if (lastMatch && lastMatch.range.equal(sel) && lastMatch.outerRange.equal(sel)) { 9157 lastMatch.range = lastMatch.innerRange; 9158 } 9159 } 9160 } 9161 } else { 9162 if ( 9163 !lastMatch.innerRange.equal(lastMatch.outerRange) 9164 && lastMatch.range.equal(lastMatch.innerRange) 9165 && sel.equal(lastMatch.range)) { 9166 lastMatch.range = lastMatch.outerRange; 9167 } else { 9168 lastMatch = matcher.find(content, sel.start); 9169 if (lastMatch && lastMatch.range.equal(sel) && lastMatch.innerRange.equal(sel)) { 9170 lastMatch.range = lastMatch.outerRange; 9171 } 9172 } 9173 } 9174 } else { 9175 lastMatch = matcher.find(content, sel.start); 9176 } 9177 9178 if (lastMatch && !lastMatch.range.equal(sel)) { 9179 editor.createSelection(lastMatch.range.start, lastMatch.range.end); 9180 return true; 9181 } 9182 9183 lastMatch = null; 9184 return false; 9185 } 9186 9187 actions.add('match_pair', matchPair, {hidden: true}); 9188 actions.add('match_pair_inward', function(editor){ 9189 return matchPair(editor, 'in'); 9190 }, {label: 'HTML/Match Pair Tag (inward)'}); 9191 9192 actions.add('match_pair_outward', function(editor){ 9193 return matchPair(editor, 'out'); 9194 }, {label: 'HTML/Match Pair Tag (outward)'}); 9195 9196 /** 9197 * Moves caret to matching opening or closing tag 9198 * @param {IEmmetEditor} editor 9199 */ 9200 actions.add('matching_pair', function(editor) { 9201 var content = String(editor.getContent()); 9202 var caretPos = editor.getCaretPos(); 9203 9204 if (content.charAt(caretPos) == '<') 9205 // looks like caret is outside of tag pair 9206 caretPos++; 9207 9208 var tag = matcher.tag(content, caretPos); 9209 if (tag && tag.close) { // exclude unary tags 9210 if (tag.open.range.inside(caretPos)) { 9211 editor.setCaretPos(tag.close.range.start); 9212 } else { 9213 editor.setCaretPos(tag.open.range.start); 9214 } 9215 9216 return true; 9217 } 9218 9219 return false; 9220 }, {label: 'HTML/Go To Matching Tag Pair'}); 9221 });/** 9222 * Gracefully removes tag under cursor 9223 * 9224 * @param {Function} require 9225 * @param {Underscore} _ 9226 */ 9227 emmet.exec(function(require, _) { 9228 require('actions').add('remove_tag', function(editor) { 9229 var utils = require('utils'); 9230 var info = require('editorUtils').outputInfo(editor); 9231 9232 // search for tag 9233 var tag = require('htmlMatcher').tag(info.content, editor.getCaretPos()); 9234 if (tag) { 9235 if (!tag.close) { 9236 // simply remove unary tag 9237 editor.replaceContent(utils.getCaretPlaceholder(), tag.range.start, tag.range.end); 9238 } else { 9239 // remove tag and its newlines 9240 /** @type Range */ 9241 var tagContentRange = utils.narrowToNonSpace(info.content, tag.innerRange); 9242 /** @type Range */ 9243 var startLineBounds = utils.findNewlineBounds(info.content, tagContentRange.start); 9244 var startLinePad = utils.getLinePadding(startLineBounds.substring(info.content)); 9245 var tagContent = tagContentRange.substring(info.content); 9246 9247 tagContent = utils.unindentString(tagContent, startLinePad); 9248 editor.replaceContent(utils.getCaretPlaceholder() + utils.escapeText(tagContent), tag.outerRange.start, tag.outerRange.end); 9249 } 9250 9251 return true; 9252 } 9253 9254 return false; 9255 }, {label: 'HTML/Remove Tag'}); 9256 }); 9257 /** 9258 * Splits or joins tag, e.g. transforms it into a short notation and vice versa:<br> 9259 * <div></div> → <div /> : join<br> 9260 * <div /> → <div></div> : split 9261 * @param {Function} require 9262 * @param {Underscore} _ 9263 * @memberOf __splitJoinTagAction 9264 * @constructor 9265 */ 9266 emmet.exec(function(require, _) { 9267 /** 9268 * @param {IEmmetEditor} editor 9269 * @param {Object} profile 9270 * @param {Object} tag 9271 */ 9272 function joinTag(editor, profile, tag) { 9273 /** @type emmet.utils */ 9274 var utils = require('utils'); 9275 9276 // empty closing slash is a nonsense for this action 9277 var slash = profile.selfClosing() || ' /'; 9278 var content = tag.open.range.substring(tag.source).replace(/\s*>$/, slash + '>'); 9279 9280 var caretPos = editor.getCaretPos(); 9281 9282 // update caret position 9283 if (content.length + tag.outerRange.start < caretPos) { 9284 caretPos = content.length + tag.outerRange.start; 9285 } 9286 9287 content = utils.escapeText(content); 9288 editor.replaceContent(content, tag.outerRange.start, tag.outerRange.end); 9289 editor.setCaretPos(caretPos); 9290 return true; 9291 } 9292 9293 function splitTag(editor, profile, tag) { 9294 /** @type emmet.utils */ 9295 var utils = require('utils'); 9296 9297 var nl = utils.getNewline(); 9298 var pad = require('resources').getVariable('indentation'); 9299 var caretPos = editor.getCaretPos(); 9300 9301 // define tag content depending on profile 9302 var tagContent = (profile.tag_nl === true) ? nl + pad + nl : ''; 9303 var content = tag.outerContent().replace(/\s*\/>$/, '>'); 9304 caretPos = tag.outerRange.start + content.length; 9305 content += tagContent + '</' + tag.open.name + '>'; 9306 9307 content = utils.escapeText(content); 9308 editor.replaceContent(content, tag.outerRange.start, tag.outerRange.end); 9309 editor.setCaretPos(caretPos); 9310 return true; 9311 } 9312 9313 require('actions').add('split_join_tag', function(editor, profileName) { 9314 var matcher = require('htmlMatcher'); 9315 9316 var info = require('editorUtils').outputInfo(editor, null, profileName); 9317 var profile = require('profile').get(info.profile); 9318 9319 // find tag at current position 9320 var tag = matcher.tag(info.content, editor.getCaretPos()); 9321 if (tag) { 9322 return tag.close 9323 ? joinTag(editor, profile, tag) 9324 : splitTag(editor, profile, tag); 9325 } 9326 9327 return false; 9328 }, {label: 'HTML/Split\\Join Tag Declaration'}); 9329 });/** 9330 * Reflect CSS value: takes rule's value under caret and pastes it for the same 9331 * rules with vendor prefixes 9332 * @constructor 9333 * @memberOf __reflectCSSActionDefine 9334 * @param {Function} require 9335 * @param {Underscore} _ 9336 */ 9337 emmet.define('reflectCSSValue', function(require, _) { 9338 /** 9339 * @type HandlerList List of registered handlers 9340 */ 9341 var handlers = require('handlerList').create(); 9342 9343 require('actions').add('reflect_css_value', function(editor) { 9344 if (editor.getSyntax() != 'css') return false; 9345 9346 return require('actionUtils').compoundUpdate(editor, doCSSReflection(editor)); 9347 }, {label: 'CSS/Reflect Value'}); 9348 9349 function doCSSReflection(editor) { 9350 /** @type emmet.cssEditTree */ 9351 var cssEditTree = require('cssEditTree'); 9352 var outputInfo = require('editorUtils').outputInfo(editor); 9353 var caretPos = editor.getCaretPos(); 9354 9355 var cssRule = cssEditTree.parseFromPosition(outputInfo.content, caretPos); 9356 if (!cssRule) return; 9357 9358 var property = cssRule.itemFromPosition(caretPos, true); 9359 // no property under cursor, nothing to reflect 9360 if (!property) return; 9361 9362 var oldRule = cssRule.source; 9363 var offset = cssRule.options.offset; 9364 var caretDelta = caretPos - offset - property.range().start; 9365 9366 handlers.exec(false, [property]); 9367 9368 if (oldRule !== cssRule.source) { 9369 return { 9370 data: cssRule.source, 9371 start: offset, 9372 end: offset + oldRule.length, 9373 caret: offset + property.range().start + caretDelta 9374 }; 9375 } 9376 } 9377 9378 /** 9379 * Returns regexp that should match reflected CSS property names 9380 * @param {String} name Current CSS property name 9381 * @return {RegExp} 9382 */ 9383 function getReflectedCSSName(name) { 9384 name = require('cssEditTree').baseName(name); 9385 var vendorPrefix = '^(?:\\-\\w+\\-)?', m; 9386 9387 if (name == 'opacity' || name == 'filter') { 9388 return new RegExp(vendorPrefix + '(?:opacity|filter)$'); 9389 } else if ((m = name.match(/^border-radius-(top|bottom)(left|right)/))) { 9390 // Mozilla-style border radius 9391 return new RegExp(vendorPrefix + '(?:' + name + '|border-' + m[1] + '-' + m[2] + '-radius)$'); 9392 } else if ((m = name.match(/^border-(top|bottom)-(left|right)-radius/))) { 9393 return new RegExp(vendorPrefix + '(?:' + name + '|border-radius-' + m[1] + m[2] + ')$'); 9394 } 9395 9396 return new RegExp(vendorPrefix + name + '$'); 9397 } 9398 9399 /** 9400 * Reflects value from <code>donor</code> into <code>receiver</code> 9401 * @param {CSSProperty} donor Donor CSS property from which value should 9402 * be reflected 9403 * @param {CSSProperty} receiver Property that should receive reflected 9404 * value from donor 9405 */ 9406 function reflectValue(donor, receiver) { 9407 var value = getReflectedValue(donor.name(), donor.value(), 9408 receiver.name(), receiver.value()); 9409 9410 receiver.value(value); 9411 } 9412 9413 /** 9414 * Returns value that should be reflected for <code>refName</code> CSS property 9415 * from <code>curName</code> property. This function is used for special cases, 9416 * when the same result must be achieved with different properties for different 9417 * browsers. For example: opаcity:0.5; → filter:alpha(opacity=50);<br><br> 9418 * 9419 * This function does value conversion between different CSS properties 9420 * 9421 * @param {String} curName Current CSS property name 9422 * @param {String} curValue Current CSS property value 9423 * @param {String} refName Receiver CSS property's name 9424 * @param {String} refValue Receiver CSS property's value 9425 * @return {String} New value for receiver property 9426 */ 9427 function getReflectedValue(curName, curValue, refName, refValue) { 9428 var cssEditTree = require('cssEditTree'); 9429 var utils = require('utils'); 9430 curName = cssEditTree.baseName(curName); 9431 refName = cssEditTree.baseName(refName); 9432 9433 if (curName == 'opacity' && refName == 'filter') { 9434 return refValue.replace(/opacity=[^)]*/i, 'opacity=' + Math.floor(parseFloat(curValue) * 100)); 9435 } else if (curName == 'filter' && refName == 'opacity') { 9436 var m = curValue.match(/opacity=([^)]*)/i); 9437 return m ? utils.prettifyNumber(parseInt(m[1], 10) / 100) : refValue; 9438 } 9439 9440 return curValue; 9441 } 9442 9443 // XXX add default handler 9444 handlers.add(function(property) { 9445 var reName = getReflectedCSSName(property.name()); 9446 _.each(property.parent.list(), function(p) { 9447 if (reName.test(p.name())) { 9448 reflectValue(property, p); 9449 } 9450 }); 9451 }, {order: -1}); 9452 9453 return { 9454 /** 9455 * Adds custom reflect handler. The passed function will receive matched 9456 * CSS property (as <code>CSSEditElement</code> object) and should 9457 * return <code>true</code> if it was performed successfully (handled 9458 * reflection), <code>false</code> otherwise. 9459 * @param {Function} fn 9460 * @param {Object} options 9461 */ 9462 addHandler: function(fn, options) { 9463 handlers.add(fn, options); 9464 }, 9465 9466 /** 9467 * Removes registered handler 9468 * @returns 9469 */ 9470 removeHandler: function(fn) { 9471 handlers.remove(fn); 9472 } 9473 }; 9474 });/** 9475 * Evaluates simple math expression under caret 9476 * @param {Function} require 9477 * @param {Underscore} _ 9478 */ 9479 emmet.exec(function(require, _) { 9480 require('actions').add('evaluate_math_expression', function(editor) { 9481 var actionUtils = require('actionUtils'); 9482 var utils = require('utils'); 9483 9484 var content = String(editor.getContent()); 9485 var chars = '.+-*/\\'; 9486 9487 /** @type Range */ 9488 var sel = require('range').create(editor.getSelectionRange()); 9489 if (!sel.length()) { 9490 sel = actionUtils.findExpressionBounds(editor, function(ch) { 9491 return utils.isNumeric(ch) || chars.indexOf(ch) != -1; 9492 }); 9493 } 9494 9495 if (sel && sel.length()) { 9496 var expr = sel.substring(content); 9497 9498 // replace integral division: 11\2 => Math.round(11/2) 9499 expr = expr.replace(/([\d\.\-]+)\\([\d\.\-]+)/g, 'Math.round($1/$2)'); 9500 9501 try { 9502 var result = utils.prettifyNumber(new Function('return ' + expr)()); 9503 editor.replaceContent(result, sel.start, sel.end); 9504 editor.setCaretPos(sel.start + result.length); 9505 return true; 9506 } catch (e) {} 9507 } 9508 9509 return false; 9510 }, {label: 'Numbers/Evaluate Math Expression'}); 9511 }); 9512 /** 9513 * Increment/decrement number under cursor 9514 * @param {Function} require 9515 * @param {Underscore} _ 9516 */ 9517 emmet.exec(function(require, _) { 9518 /** 9519 * Extract number from current caret position of the <code>editor</code> and 9520 * increment it by <code>step</code> 9521 * @param {IEmmetEditor} editor 9522 * @param {Number} step Increment step (may be negative) 9523 */ 9524 function incrementNumber(editor, step) { 9525 var utils = require('utils'); 9526 var actionUtils = require('actionUtils'); 9527 9528 var hasSign = false; 9529 var hasDecimal = false; 9530 9531 var r = actionUtils.findExpressionBounds(editor, function(ch, pos, content) { 9532 if (utils.isNumeric(ch)) 9533 return true; 9534 if (ch == '.') { 9535 // make sure that next character is numeric too 9536 if (!utils.isNumeric(content.charAt(pos + 1))) 9537 return false; 9538 9539 return hasDecimal ? false : hasDecimal = true; 9540 } 9541 if (ch == '-') 9542 return hasSign ? false : hasSign = true; 9543 9544 return false; 9545 }); 9546 9547 if (r && r.length()) { 9548 var strNum = r.substring(String(editor.getContent())); 9549 var num = parseFloat(strNum); 9550 if (!_.isNaN(num)) { 9551 num = utils.prettifyNumber(num + step); 9552 9553 // do we have zero-padded number? 9554 if (/^(\-?)0+[1-9]/.test(strNum)) { 9555 var minus = ''; 9556 if (RegExp.$1) { 9557 minus = '-'; 9558 num = num.substring(1); 9559 } 9560 9561 var parts = num.split('.'); 9562 parts[0] = utils.zeroPadString(parts[0], intLength(strNum)); 9563 num = minus + parts.join('.'); 9564 } 9565 9566 editor.replaceContent(num, r.start, r.end); 9567 editor.createSelection(r.start, r.start + num.length); 9568 return true; 9569 } 9570 } 9571 9572 return false; 9573 } 9574 9575 /** 9576 * Returns length of integer part of number 9577 * @param {String} num 9578 */ 9579 function intLength(num) { 9580 num = num.replace(/^\-/, ''); 9581 if (~num.indexOf('.')) { 9582 return num.split('.')[0].length; 9583 } 9584 9585 return num.length; 9586 } 9587 9588 var actions = require('actions'); 9589 _.each([1, -1, 10, -10, 0.1, -0.1], function(num) { 9590 var prefix = num > 0 ? 'increment' : 'decrement'; 9591 9592 actions.add(prefix + '_number_by_' + String(Math.abs(num)).replace('.', '').substring(0, 2), function(editor) { 9593 return incrementNumber(editor, num); 9594 }, {label: 'Numbers/' + prefix.charAt(0).toUpperCase() + prefix.substring(1) + ' number by ' + Math.abs(num)}); 9595 }); 9596 });/** 9597 * Actions to insert line breaks. Some simple editors (like browser's 9598 * <textarea>, for example) do not provide such simple things 9599 * @param {Function} require 9600 * @param {Underscore} _ 9601 */ 9602 emmet.exec(function(require, _) { 9603 var actions = require('actions'); 9604 /** @type emmet.preferences */ 9605 var prefs = require('preferences'); 9606 9607 // setup default preferences 9608 prefs.define('css.closeBraceIndentation', '\n', 9609 'Indentation before closing brace of CSS rule. Some users prefere ' 9610 + 'indented closing brace of CSS rule for better readability. ' 9611 + 'This preference’s value will be automatically inserted before ' 9612 + 'closing brace when user adds newline in newly created CSS rule ' 9613 + '(e.g. when “Insert formatted linebreak” action will be performed ' 9614 + 'in CSS file). If you’re such user, you may want to write put a value ' 9615 + 'like <code>\\n\\t</code> in this preference.'); 9616 9617 /** 9618 * Inserts newline character with proper indentation in specific positions only. 9619 * @param {IEmmetEditor} editor 9620 * @return {Boolean} Returns <code>true</code> if line break was inserted 9621 */ 9622 actions.add('insert_formatted_line_break_only', function(editor) { 9623 var utils = require('utils'); 9624 /** @type emmet.resources */ 9625 var res = require('resources'); 9626 9627 var info = require('editorUtils').outputInfo(editor); 9628 var caretPos = editor.getCaretPos(); 9629 var nl = utils.getNewline(); 9630 var pad; 9631 9632 if (_.include(['html', 'xml', 'xsl'], info.syntax)) { 9633 pad = res.getVariable('indentation'); 9634 // let's see if we're breaking newly created tag 9635 var tag = require('htmlMatcher').tag(info.content, caretPos); 9636 if (tag && !tag.innerRange.length()) { 9637 editor.replaceContent(nl + pad + utils.getCaretPlaceholder() + nl, caretPos); 9638 return true; 9639 } 9640 } else if (info.syntax == 'css') { 9641 /** @type String */ 9642 var content = info.content; 9643 if (caretPos && content.charAt(caretPos - 1) == '{') { 9644 var append = prefs.get('css.closeBraceIndentation'); 9645 pad = res.getVariable('indentation'); 9646 9647 var hasCloseBrace = content.charAt(caretPos) == '}'; 9648 if (!hasCloseBrace) { 9649 // do we really need special formatting here? 9650 // check if this is really a newly created rule, 9651 // look ahead for a closing brace 9652 for (var i = caretPos, il = content.length, ch; i < il; i++) { 9653 ch = content.charAt(i); 9654 if (ch == '{') { 9655 // ok, this is a new rule without closing brace 9656 break; 9657 } 9658 9659 if (ch == '}') { 9660 // not a new rule, just add indentation 9661 append = ''; 9662 hasCloseBrace = true; 9663 break; 9664 } 9665 } 9666 } 9667 9668 if (!hasCloseBrace) { 9669 append += '}'; 9670 } 9671 9672 // defining rule set 9673 var insValue = nl + pad + utils.getCaretPlaceholder() + append; 9674 editor.replaceContent(insValue, caretPos); 9675 return true; 9676 } 9677 } 9678 9679 return false; 9680 }, {hidden: true}); 9681 9682 /** 9683 * Inserts newline character with proper indentation. This action is used in 9684 * editors that doesn't have indentation control (like textarea element) to 9685 * provide proper indentation 9686 * @param {IEmmetEditor} editor Editor instance 9687 */ 9688 actions.add('insert_formatted_line_break', function(editor) { 9689 if (!actions.run('insert_formatted_line_break_only', editor)) { 9690 var utils = require('utils'); 9691 9692 var curPadding = require('editorUtils').getCurrentLinePadding(editor); 9693 var content = String(editor.getContent()); 9694 var caretPos = editor.getCaretPos(); 9695 var len = content.length; 9696 var nl = utils.getNewline(); 9697 9698 // check out next line padding 9699 var lineRange = editor.getCurrentLineRange(); 9700 var nextPadding = ''; 9701 9702 for (var i = lineRange.end + 1, ch; i < len; i++) { 9703 ch = content.charAt(i); 9704 if (ch == ' ' || ch == '\t') 9705 nextPadding += ch; 9706 else 9707 break; 9708 } 9709 9710 if (nextPadding.length > curPadding.length) 9711 editor.replaceContent(nl + nextPadding, caretPos, caretPos, true); 9712 else 9713 editor.replaceContent(nl, caretPos); 9714 } 9715 9716 return true; 9717 }, {hidden: true}); 9718 });/** 9719 * Merges selected lines or lines between XHTML tag pairs 9720 * @param {Function} require 9721 * @param {Underscore} _ 9722 */ 9723 emmet.exec(function(require, _) { 9724 require('actions').add('merge_lines', function(editor) { 9725 var matcher = require('htmlMatcher'); 9726 var utils = require('utils'); 9727 var editorUtils = require('editorUtils'); 9728 var info = editorUtils.outputInfo(editor); 9729 9730 /** @type Range */ 9731 var selection = require('range').create(editor.getSelectionRange()); 9732 if (!selection.length()) { 9733 // find matching tag 9734 var pair = matcher.find(info.content, editor.getCaretPos()); 9735 if (pair) { 9736 selection = pair.outerRange; 9737 } 9738 } 9739 9740 if (selection.length()) { 9741 // got range, merge lines 9742 var text = selection.substring(info.content); 9743 var lines = utils.splitByLines(text); 9744 9745 for (var i = 1; i < lines.length; i++) { 9746 lines[i] = lines[i].replace(/^\s+/, ''); 9747 } 9748 9749 text = lines.join('').replace(/\s{2,}/, ' '); 9750 var textLen = text.length; 9751 text = utils.escapeText(text); 9752 editor.replaceContent(text, selection.start, selection.end); 9753 editor.createSelection(selection.start, selection.start + textLen); 9754 9755 return true; 9756 } 9757 9758 return false; 9759 }); 9760 });/** 9761 * Encodes/decodes image under cursor to/from base64 9762 * @param {IEmmetEditor} editor 9763 * @since 0.65 9764 * 9765 * @memberOf __base64ActionDefine 9766 * @constructor 9767 * @param {Function} require 9768 * @param {Underscore} _ 9769 */ 9770 emmet.exec(function(require, _) { 9771 require('actions').add('encode_decode_data_url', function(editor) { 9772 var data = String(editor.getSelection()); 9773 var caretPos = editor.getCaretPos(); 9774 9775 if (!data) { 9776 // no selection, try to find image bounds from current caret position 9777 var text = String(editor.getContent()), m; 9778 while (caretPos-- >= 0) { 9779 if (startsWith('src=', text, caretPos)) { // found <img src=""> 9780 if ((m = text.substr(caretPos).match(/^(src=(["'])?)([^'"<>\s]+)\1?/))) { 9781 data = m[3]; 9782 caretPos += m[1].length; 9783 } 9784 break; 9785 } else if (startsWith('url(', text, caretPos)) { // found CSS url() pattern 9786 if ((m = text.substr(caretPos).match(/^(url\((['"])?)([^'"\)\s]+)\1?/))) { 9787 data = m[3]; 9788 caretPos += m[1].length; 9789 } 9790 break; 9791 } 9792 } 9793 } 9794 9795 if (data) { 9796 if (startsWith('data:', data)) 9797 return decodeFromBase64(editor, data, caretPos); 9798 else 9799 return encodeToBase64(editor, data, caretPos); 9800 } 9801 9802 return false; 9803 }, {label: 'Encode\\Decode data:URL image'}); 9804 9805 /** 9806 * Test if <code>text</code> starts with <code>token</code> at <code>pos</code> 9807 * position. If <code>pos</code> is omitted, search from beginning of text 9808 * @param {String} token Token to test 9809 * @param {String} text Where to search 9810 * @param {Number} pos Position where to start search 9811 * @return {Boolean} 9812 * @since 0.65 9813 */ 9814 function startsWith(token, text, pos) { 9815 pos = pos || 0; 9816 return text.charAt(pos) == token.charAt(0) && text.substr(pos, token.length) == token; 9817 } 9818 9819 /** 9820 * Encodes image to base64 9821 * 9822 * @param {IEmmetEditor} editor 9823 * @param {String} imgPath Path to image 9824 * @param {Number} pos Caret position where image is located in the editor 9825 * @return {Boolean} 9826 */ 9827 function encodeToBase64(editor, imgPath, pos) { 9828 var file = require('file'); 9829 var actionUtils = require('actionUtils'); 9830 9831 var editorFile = editor.getFilePath(); 9832 var defaultMimeType = 'application/octet-stream'; 9833 9834 if (editorFile === null) { 9835 throw "You should save your file before using this action"; 9836 } 9837 9838 // locate real image path 9839 var realImgPath = file.locateFile(editorFile, imgPath); 9840 if (realImgPath === null) { 9841 throw "Can't find " + imgPath + ' file'; 9842 } 9843 9844 file.read(realImgPath, function(err, content) { 9845 if (err) { 9846 throw 'Unable to read ' + realImgPath + ': ' + err; 9847 } 9848 9849 var b64 = require('base64').encode(String(content)); 9850 if (!b64) { 9851 throw "Can't encode file content to base64"; 9852 } 9853 9854 b64 = 'data:' + (actionUtils.mimeTypes[String(file.getExt(realImgPath))] || defaultMimeType) + 9855 ';base64,' + b64; 9856 9857 editor.replaceContent('$0' + b64, pos, pos + imgPath.length); 9858 }); 9859 9860 9861 return true; 9862 } 9863 9864 /** 9865 * Decodes base64 string back to file. 9866 * @param {IEmmetEditor} editor 9867 * @param {String} data Base64-encoded file content 9868 * @param {Number} pos Caret position where image is located in the editor 9869 */ 9870 function decodeFromBase64(editor, data, pos) { 9871 // ask user to enter path to file 9872 var filePath = String(editor.prompt('Enter path to file (absolute or relative)')); 9873 if (!filePath) 9874 return false; 9875 9876 var file = require('file'); 9877 var absPath = file.createPath(editor.getFilePath(), filePath); 9878 if (!absPath) { 9879 throw "Can't save file"; 9880 } 9881 9882 file.save(absPath, require('base64').decode( data.replace(/^data\:.+?;.+?,/, '') )); 9883 editor.replaceContent('$0' + filePath, pos, pos + data.length); 9884 return true; 9885 } 9886 }); 9887 /** 9888 * Automatically updates image size attributes in HTML's <img> element or 9889 * CSS rule 9890 * @param {Function} require 9891 * @param {Underscore} _ 9892 * @constructor 9893 * @memberOf __updateImageSizeAction 9894 */ 9895 emmet.exec(function(require, _) { 9896 /** 9897 * Updates image size of <img src=""> tag 9898 * @param {IEmmetEditor} editor 9899 */ 9900 function updateImageSizeHTML(editor) { 9901 var offset = editor.getCaretPos(); 9902 9903 // find tag from current caret position 9904 var info = require('editorUtils').outputInfo(editor); 9905 var xmlElem = require('xmlEditTree').parseFromPosition(info.content, offset, true); 9906 if (xmlElem && (xmlElem.name() || '').toLowerCase() == 'img') { 9907 getImageSizeForSource(editor, xmlElem.value('src'), function(size) { 9908 if (size) { 9909 var compoundData = xmlElem.range(true); 9910 xmlElem.value('width', size.width); 9911 xmlElem.value('height', size.height, xmlElem.indexOf('width') + 1); 9912 9913 require('actionUtils').compoundUpdate(editor, _.extend(compoundData, { 9914 data: xmlElem.toString(), 9915 caret: offset 9916 })); 9917 } 9918 }); 9919 } 9920 } 9921 9922 /** 9923 * Updates image size of CSS property 9924 * @param {IEmmetEditor} editor 9925 */ 9926 function updateImageSizeCSS(editor) { 9927 var offset = editor.getCaretPos(); 9928 9929 // find tag from current caret position 9930 var info = require('editorUtils').outputInfo(editor); 9931 var cssRule = require('cssEditTree').parseFromPosition(info.content, offset, true); 9932 if (cssRule) { 9933 // check if there is property with image under caret 9934 var prop = cssRule.itemFromPosition(offset, true), m; 9935 if (prop && (m = /url\((["']?)(.+?)\1\)/i.exec(prop.value() || ''))) { 9936 getImageSizeForSource(editor, m[2], function(size) { 9937 if (size) { 9938 var compoundData = cssRule.range(true); 9939 cssRule.value('width', size.width + 'px'); 9940 cssRule.value('height', size.height + 'px', cssRule.indexOf('width') + 1); 9941 9942 require('actionUtils').compoundUpdate(editor, _.extend(compoundData, { 9943 data: cssRule.toString(), 9944 caret: offset 9945 })); 9946 } 9947 }); 9948 } 9949 } 9950 } 9951 9952 /** 9953 * Returns image dimensions for source 9954 * @param {IEmmetEditor} editor 9955 * @param {String} src Image source (path or data:url) 9956 */ 9957 function getImageSizeForSource(editor, src, callback) { 9958 var fileContent; 9959 var au = require('actionUtils'); 9960 if (src) { 9961 // check if it is data:url 9962 if (/^data:/.test(src)) { 9963 fileContent = require('base64').decode( src.replace(/^data\:.+?;.+?,/, '') ); 9964 return callback(au.getImageSize(fileContent)); 9965 } 9966 9967 var file = require('file'); 9968 var absPath = file.locateFile(editor.getFilePath(), src); 9969 if (absPath === null) { 9970 throw "Can't find " + src + ' file'; 9971 } 9972 9973 file.read(absPath, function(err, content) { 9974 if (err) { 9975 throw 'Unable to read ' + absPath + ': ' + err; 9976 } 9977 9978 content = String(content); 9979 callback(au.getImageSize(content)); 9980 }); 9981 } 9982 } 9983 9984 require('actions').add('update_image_size', function(editor) { 9985 // this action will definitely won’t work in SASS dialect, 9986 // but may work in SCSS or LESS 9987 if (_.include(['css', 'less', 'scss'], String(editor.getSyntax()))) { 9988 updateImageSizeCSS(editor); 9989 } else { 9990 updateImageSizeHTML(editor); 9991 } 9992 9993 return true; 9994 }); 9995 });/** 9996 * Resolver for fast CSS typing. Handles abbreviations with the following 9997 * notation:<br> 9998 * 9999 * <code>(-vendor prefix)?property(value)*(!)?</code> 10000 * 10001 * <br><br> 10002 * <b>Abbreviation handling</b><br> 10003 * 10004 * By default, Emmet searches for matching snippet definition for provided abbreviation. 10005 * If snippet wasn't found, Emmet automatically generates element with 10006 * abbreviation's name. For example, <code>foo</code> abbreviation will generate 10007 * <code><foo></foo></code> output. 10008 * <br><br> 10009 * This module will capture all expanded properties and upgrade them with values, 10010 * vendor prefixes and !important declarations. All unmatched abbreviations will 10011 * be automatically transformed into <code>property-name: ${1}</code> snippets. 10012 * 10013 * <b>Vendor prefixes<b><br> 10014 * 10015 * If CSS-property is preceded with dash, resolver should output property with 10016 * all <i>known</i> vendor prefixes. For example, if <code>brad</code> 10017 * abbreviation generates <code>border-radius: ${value};</code> snippet, 10018 * the <code>-brad</code> abbreviation should generate: 10019 * <pre><code> 10020 * -webkit-border-radius: ${value}; 10021 * -moz-border-radius: ${value}; 10022 * border-radius: ${value}; 10023 * </code></pre> 10024 * Note that <i>o</i> and <i>ms</i> prefixes are omitted since Opera and IE 10025 * supports unprefixed property.<br><br> 10026 * 10027 * Users can also provide an explicit list of one-character prefixes for any 10028 * CSS property. For example, <code>-wm-float</code> will produce 10029 * 10030 * <pre><code> 10031 * -webkit-float: ${1}; 10032 * -moz-float: ${1}; 10033 * float: ${1}; 10034 * </code></pre> 10035 * 10036 * Although this example looks pointless, users can use this feature to write 10037 * cutting-edge properties implemented by browser vendors recently. 10038 * 10039 * @constructor 10040 * @memberOf __cssResolverDefine 10041 * @param {Function} require 10042 * @param {Underscore} _ 10043 */ 10044 emmet.define('cssResolver', function(require, _) { 10045 /** Back-reference to module */ 10046 var module = null; 10047 10048 var prefixObj = { 10049 /** Real vendor prefix name */ 10050 prefix: 'emmet', 10051 10052 /** 10053 * Indicates this prefix is obsolete and should't be used when user 10054 * wants to generate all-prefixed properties 10055 */ 10056 obsolete: false, 10057 10058 /** 10059 * Returns prefixed CSS property name 10060 * @param {String} name Unprefixed CSS property 10061 */ 10062 transformName: function(name) { 10063 return '-' + this.prefix + '-' + name; 10064 }, 10065 10066 /** 10067 * List of unprefixed CSS properties that supported by 10068 * current prefix. This list is used to generate all-prefixed property 10069 * @returns {Array} 10070 */ 10071 properties: function() { 10072 return getProperties('css.' + this.prefix + 'Properties') || []; 10073 }, 10074 10075 /** 10076 * Check if given property is supported by current prefix 10077 * @param name 10078 */ 10079 supports: function(name) { 10080 return _.include(this.properties(), name); 10081 } 10082 }; 10083 10084 10085 /** 10086 * List of registered one-character prefixes. Key is a one-character prefix, 10087 * value is an <code>prefixObj</code> object 10088 */ 10089 var vendorPrefixes = {}; 10090 10091 var defaultValue = '${1};'; 10092 10093 // XXX module preferences 10094 var prefs = require('preferences'); 10095 prefs.define('css.valueSeparator', ': ', 10096 'Defines a symbol that should be placed between CSS property and ' 10097 + 'value when expanding CSS abbreviations.'); 10098 prefs.define('css.propertyEnd', ';', 10099 'Defines a symbol that should be placed at the end of CSS property ' 10100 + 'when expanding CSS abbreviations.'); 10101 10102 prefs.define('stylus.valueSeparator', ' ', 10103 'Defines a symbol that should be placed between CSS property and ' 10104 + 'value when expanding CSS abbreviations in Stylus dialect.'); 10105 prefs.define('stylus.propertyEnd', '', 10106 'Defines a symbol that should be placed at the end of CSS property ' 10107 + 'when expanding CSS abbreviations in Stylus dialect.'); 10108 10109 prefs.define('sass.propertyEnd', '', 10110 'Defines a symbol that should be placed at the end of CSS property ' 10111 + 'when expanding CSS abbreviations in SASS dialect.'); 10112 10113 prefs.define('css.syntaxes', 'css, less, sass, scss, stylus, styl', 10114 'List of syntaxes that should be treated as CSS dialects.'); 10115 10116 prefs.define('css.autoInsertVendorPrefixes', true, 10117 'Automatically generate vendor-prefixed copies of expanded CSS ' 10118 + 'property. By default, Emmet will generate vendor-prefixed ' 10119 + 'properties only when you put dash before abbreviation ' 10120 + '(e.g. <code>-bxsh</code>). With this option enabled, you don’t ' 10121 + 'need dashes before abbreviations: Emmet will produce ' 10122 + 'vendor-prefixed properties for you.'); 10123 10124 var descTemplate = _.template('A comma-separated list of CSS properties that may have ' 10125 + '<code><%= vendor %></code> vendor prefix. This list is used to generate ' 10126 + 'a list of prefixed properties when expanding <code>-property</code> ' 10127 + 'abbreviations. Empty list means that all possible CSS values may ' 10128 + 'have <code><%= vendor %></code> prefix.'); 10129 10130 var descAddonTemplate = _.template('A comma-separated list of <em>additional</em> CSS properties ' 10131 + 'for <code>css.<%= vendor %>Preperties</code> preference. ' 10132 + 'You should use this list if you want to add or remove a few CSS ' 10133 + 'properties to original set. To add a new property, simply write its name, ' 10134 + 'to remove it, precede property with hyphen.<br>' 10135 + 'For example, to add <em>foo</em> property and remove <em>border-radius</em> one, ' 10136 + 'the preference value will look like this: <code>foo, -border-radius</code>.'); 10137 10138 // properties list is created from cssFeatures.html file 10139 var props = { 10140 'webkit': 'animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, appearance, backface-visibility, background-clip, background-composite, background-origin, background-size, border-fit, border-horizontal-spacing, border-image, border-vertical-spacing, box-align, box-direction, box-flex, box-flex-group, box-lines, box-ordinal-group, box-orient, box-pack, box-reflect, box-shadow, color-correction, column-break-after, column-break-before, column-break-inside, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-span, column-width, dashboard-region, font-smoothing, highlight, hyphenate-character, hyphenate-limit-after, hyphenate-limit-before, hyphens, line-box-contain, line-break, line-clamp, locale, margin-before-collapse, margin-after-collapse, marquee-direction, marquee-increment, marquee-repetition, marquee-style, mask-attachment, mask-box-image, mask-box-image-outset, mask-box-image-repeat, mask-box-image-slice, mask-box-image-source, mask-box-image-width, mask-clip, mask-composite, mask-image, mask-origin, mask-position, mask-repeat, mask-size, nbsp-mode, perspective, perspective-origin, rtl-ordering, text-combine, text-decorations-in-effect, text-emphasis-color, text-emphasis-position, text-emphasis-style, text-fill-color, text-orientation, text-security, text-stroke-color, text-stroke-width, transform, transition, transform-origin, transform-style, transition-delay, transition-duration, transition-property, transition-timing-function, user-drag, user-modify, user-select, writing-mode, svg-shadow, box-sizing, border-radius', 10141 'moz': 'animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, appearance, backface-visibility, background-inline-policy, binding, border-bottom-colors, border-image, border-left-colors, border-right-colors, border-top-colors, box-align, box-direction, box-flex, box-ordinal-group, box-orient, box-pack, box-shadow, box-sizing, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-width, float-edge, font-feature-settings, font-language-override, force-broken-image-icon, hyphens, image-region, orient, outline-radius-bottomleft, outline-radius-bottomright, outline-radius-topleft, outline-radius-topright, perspective, perspective-origin, stack-sizing, tab-size, text-blink, text-decoration-color, text-decoration-line, text-decoration-style, text-size-adjust, transform, transform-origin, transform-style, transition, transition-delay, transition-duration, transition-property, transition-timing-function, user-focus, user-input, user-modify, user-select, window-shadow, background-clip, border-radius', 10142 'ms': 'accelerator, backface-visibility, background-position-x, background-position-y, behavior, block-progression, box-align, box-direction, box-flex, box-line-progression, box-lines, box-ordinal-group, box-orient, box-pack, content-zoom-boundary, content-zoom-boundary-max, content-zoom-boundary-min, content-zoom-chaining, content-zoom-snap, content-zoom-snap-points, content-zoom-snap-type, content-zooming, filter, flow-from, flow-into, font-feature-settings, grid-column, grid-column-align, grid-column-span, grid-columns, grid-layer, grid-row, grid-row-align, grid-row-span, grid-rows, high-contrast-adjust, hyphenate-limit-chars, hyphenate-limit-lines, hyphenate-limit-zone, hyphens, ime-mode, interpolation-mode, layout-flow, layout-grid, layout-grid-char, layout-grid-line, layout-grid-mode, layout-grid-type, line-break, overflow-style, perspective, perspective-origin, perspective-origin-x, perspective-origin-y, scroll-boundary, scroll-boundary-bottom, scroll-boundary-left, scroll-boundary-right, scroll-boundary-top, scroll-chaining, scroll-rails, scroll-snap-points-x, scroll-snap-points-y, scroll-snap-type, scroll-snap-x, scroll-snap-y, scrollbar-arrow-color, scrollbar-base-color, scrollbar-darkshadow-color, scrollbar-face-color, scrollbar-highlight-color, scrollbar-shadow-color, scrollbar-track-color, text-align-last, text-autospace, text-justify, text-kashida-space, text-overflow, text-size-adjust, text-underline-position, touch-action, transform, transform-origin, transform-origin-x, transform-origin-y, transform-origin-z, transform-style, transition, transition-delay, transition-duration, transition-property, transition-timing-function, user-select, word-break, word-wrap, wrap-flow, wrap-margin, wrap-through, writing-mode', 10143 'o': 'dashboard-region, animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, border-image, link, link-source, object-fit, object-position, tab-size, table-baseline, transform, transform-origin, transition, transition-delay, transition-duration, transition-property, transition-timing-function, accesskey, input-format, input-required, marquee-dir, marquee-loop, marquee-speed, marquee-style' 10144 }; 10145 10146 _.each(props, function(v, k) { 10147 prefs.define('css.' + k + 'Properties', v, descTemplate({vendor: k})); 10148 prefs.define('css.' + k + 'PropertiesAddon', '', descAddonTemplate({vendor: k})); 10149 }); 10150 10151 prefs.define('css.unitlessProperties', 'z-index, line-height, opacity, font-weight, zoom', 10152 'The list of properties whose values must not contain units.'); 10153 10154 prefs.define('css.intUnit', 'px', 'Default unit for integer values'); 10155 prefs.define('css.floatUnit', 'em', 'Default unit for float values'); 10156 10157 prefs.define('css.keywords', 'auto, inherit', 10158 'A comma-separated list of valid keywords that can be used in CSS abbreviations.'); 10159 10160 prefs.define('css.keywordAliases', 'a:auto, i:inherit, s:solid, da:dashed, do:dotted, t:transparent', 10161 'A comma-separated list of keyword aliases, used in CSS abbreviation. ' 10162 + 'Each alias should be defined as <code>alias:keyword_name</code>.'); 10163 10164 prefs.define('css.unitAliases', 'e:em, p:%, x:ex, r:rem', 10165 'A comma-separated list of unit aliases, used in CSS abbreviation. ' 10166 + 'Each alias should be defined as <code>alias:unit_value</code>.'); 10167 10168 prefs.define('css.color.short', true, 10169 'Should color values like <code>#ffffff</code> be shortened to ' 10170 + '<code>#fff</code> after abbreviation with color was expanded.'); 10171 10172 prefs.define('css.color.case', 'keep', 10173 'Letter case of color values generated by abbreviations with color ' 10174 + '(like <code>c#0</code>). Possible values are <code>upper</code>, ' 10175 + '<code>lower</code> and <code>keep</code>.'); 10176 10177 prefs.define('css.fuzzySearch', true, 10178 'Enable fuzzy search among CSS snippet names. When enabled, every ' 10179 + '<em>unknown</em> snippet will be scored against available snippet ' 10180 + 'names (not values or CSS properties!). The match with best score ' 10181 + 'will be used to resolve snippet value. For example, with this ' 10182 + 'preference enabled, the following abbreviations are equal: ' 10183 + '<code>ov:h</code> == <code>ov-h</code> == <code>o-h</code> == ' 10184 + '<code>oh</code>'); 10185 10186 prefs.define('css.fuzzySearchMinScore', 0.3, 10187 'The minium score (from 0 to 1) that fuzzy-matched abbreviation should ' 10188 + 'achive. Lower values may produce many false-positive matches, ' 10189 + 'higher values may reduce possible matches.'); 10190 10191 prefs.define('css.alignVendor', false, 10192 'If set to <code>true</code>, all generated vendor-prefixed properties ' 10193 + 'will be aligned by real property name.'); 10194 10195 10196 function isNumeric(ch) { 10197 var code = ch && ch.charCodeAt(0); 10198 return (ch && ch == '.' || (code > 47 && code < 58)); 10199 } 10200 10201 /** 10202 * Check if provided snippet contains only one CSS property and value. 10203 * @param {String} snippet 10204 * @returns {Boolean} 10205 */ 10206 function isSingleProperty(snippet) { 10207 var utils = require('utils'); 10208 snippet = utils.trim(snippet); 10209 10210 // check if it doesn't contain a comment and a newline 10211 if (~snippet.indexOf('/*') || /[\n\r]/.test(snippet)) { 10212 return false; 10213 } 10214 10215 // check if it's a valid snippet definition 10216 if (!/^[a-z0-9\-]+\s*\:/i.test(snippet)) { 10217 return false; 10218 } 10219 10220 snippet = require('tabStops').processText(snippet, { 10221 replaceCarets: true, 10222 tabstop: function() { 10223 return 'value'; 10224 } 10225 }); 10226 10227 return snippet.split(':').length == 2; 10228 } 10229 10230 /** 10231 * Normalizes abbreviated value to final CSS one 10232 * @param {String} value 10233 * @returns {String} 10234 */ 10235 function normalizeValue(value) { 10236 if (value.charAt(0) == '-' && !/^\-[\.\d]/.test(value)) { 10237 value = value.replace(/^\-+/, ''); 10238 } 10239 10240 if (value.charAt(0) == '#') { 10241 return normalizeHexColor(value); 10242 } 10243 10244 return getKeyword(value); 10245 } 10246 10247 function normalizeHexColor(value) { 10248 var hex = value.replace(/^#+/, '') || '0'; 10249 if (hex.toLowerCase() == 't') { 10250 return 'transparent'; 10251 } 10252 10253 var repeat = require('utils').repeatString; 10254 var color = null; 10255 switch (hex.length) { 10256 case 1: 10257 color = repeat(hex, 6); 10258 break; 10259 case 2: 10260 color = repeat(hex, 3); 10261 break; 10262 case 3: 10263 color = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); 10264 break; 10265 case 4: 10266 color = hex + hex.substr(0, 2); 10267 break; 10268 case 5: 10269 color = hex + hex.charAt(0); 10270 break; 10271 default: 10272 color = hex.substr(0, 6); 10273 } 10274 10275 // color must be shortened? 10276 if (prefs.get('css.color.short')) { 10277 var p = color.split(''); 10278 if (p[0] == p[1] && p[2] == p[3] && p[4] == p[5]) { 10279 color = p[0] + p[2] + p[4]; 10280 } 10281 } 10282 10283 // should transform case? 10284 switch (prefs.get('css.color.case')) { 10285 case 'upper': 10286 color = color.toUpperCase(); 10287 break; 10288 case 'lower': 10289 color = color.toLowerCase(); 10290 break; 10291 } 10292 10293 return '#' + color; 10294 } 10295 10296 function getKeyword(name) { 10297 var aliases = prefs.getDict('css.keywordAliases'); 10298 return name in aliases ? aliases[name] : name; 10299 } 10300 10301 function getUnit(name) { 10302 var aliases = prefs.getDict('css.unitAliases'); 10303 return name in aliases ? aliases[name] : name; 10304 } 10305 10306 function isValidKeyword(keyword) { 10307 return _.include(prefs.getArray('css.keywords'), getKeyword(keyword)); 10308 } 10309 10310 /** 10311 * Check if passed CSS property support specified vendor prefix 10312 * @param {String} property 10313 * @param {String} prefix 10314 */ 10315 function hasPrefix(property, prefix) { 10316 var info = vendorPrefixes[prefix]; 10317 10318 if (!info) 10319 info = _.find(vendorPrefixes, function(data) { 10320 return data.prefix == prefix; 10321 }); 10322 10323 return info && info.supports(property); 10324 } 10325 10326 /** 10327 * Search for a list of supported prefixes for CSS property. This list 10328 * is used to generate all-prefixed snippet 10329 * @param {String} property CSS property name 10330 * @returns {Array} 10331 */ 10332 function findPrefixes(property, noAutofill) { 10333 var result = []; 10334 _.each(vendorPrefixes, function(obj, prefix) { 10335 if (hasPrefix(property, prefix)) { 10336 result.push(prefix); 10337 } 10338 }); 10339 10340 if (!result.length && !noAutofill) { 10341 // add all non-obsolete prefixes 10342 _.each(vendorPrefixes, function(obj, prefix) { 10343 if (!obj.obsolete) 10344 result.push(prefix); 10345 }); 10346 } 10347 10348 return result; 10349 } 10350 10351 function addPrefix(name, obj) { 10352 if (_.isString(obj)) 10353 obj = {prefix: obj}; 10354 10355 vendorPrefixes[name] = _.extend({}, prefixObj, obj); 10356 } 10357 10358 function getSyntaxPreference(name, syntax) { 10359 if (syntax) { 10360 // hacky alias for Stylus dialect 10361 if (syntax == 'styl') { 10362 syntax = 'stylus'; 10363 } 10364 10365 var val = prefs.get(syntax + '.' + name); 10366 if (!_.isUndefined(val)) 10367 return val; 10368 } 10369 10370 return prefs.get('css.' + name); 10371 } 10372 10373 /** 10374 * Format CSS property according to current syntax dialect 10375 * @param {String} property 10376 * @param {String} syntax 10377 * @returns {String} 10378 */ 10379 function formatProperty(property, syntax) { 10380 var ix = property.indexOf(':'); 10381 property = property.substring(0, ix).replace(/\s+$/, '') 10382 + getSyntaxPreference('valueSeparator', syntax) 10383 + require('utils').trim(property.substring(ix + 1)); 10384 10385 return property.replace(/\s*;\s*$/, getSyntaxPreference('propertyEnd', syntax)); 10386 } 10387 10388 /** 10389 * Transforms snippet value if required. For example, this transformation 10390 * may add <i>!important</i> declaration to CSS property 10391 * @param {String} snippet 10392 * @param {Boolean} isImportant 10393 * @returns {String} 10394 */ 10395 function transformSnippet(snippet, isImportant, syntax) { 10396 if (!_.isString(snippet)) 10397 snippet = snippet.data; 10398 10399 if (!isSingleProperty(snippet)) 10400 return snippet; 10401 10402 if (isImportant) { 10403 if (~snippet.indexOf(';')) { 10404 snippet = snippet.split(';').join(' !important;'); 10405 } else { 10406 snippet += ' !important'; 10407 } 10408 } 10409 10410 return formatProperty(snippet, syntax); 10411 } 10412 10413 function getProperties(key) { 10414 var list = prefs.getArray(key); 10415 _.each(prefs.getArray(key + 'Addon'), function(prop) { 10416 if (prop.charAt(0) == '-') { 10417 list = _.without(list, prop.substr(1)); 10418 } else { 10419 if (prop.charAt(0) == '+') 10420 prop = prop.substr(1); 10421 10422 list.push(prop); 10423 } 10424 }); 10425 10426 return list; 10427 } 10428 10429 10430 // TODO refactor, this looks awkward now 10431 addPrefix('w', { 10432 prefix: 'webkit' 10433 }); 10434 addPrefix('m', { 10435 prefix: 'moz' 10436 }); 10437 addPrefix('s', { 10438 prefix: 'ms' 10439 }); 10440 addPrefix('o', { 10441 prefix: 'o' 10442 }); 10443 10444 /** 10445 * XXX register resolver 10446 * @param {TreeNode} node 10447 * @param {String} syntax 10448 */ 10449 require('resources').addResolver(function(node, syntax) { 10450 var cssSyntaxes = prefs.getArray('css.syntaxes'); 10451 if (_.include(cssSyntaxes, syntax) && node.isElement()) { 10452 return module.expandToSnippet(node.abbreviation, syntax); 10453 } 10454 10455 return null; 10456 }); 10457 10458 var ea = require('expandAbbreviation'); 10459 /** 10460 * For CSS-like syntaxes, we need to handle a special use case. Some editors 10461 * (like Sublime Text 2) may insert semicolons automatically when user types 10462 * abbreviation. After expansion, user receives a double semicolon. This 10463 * handler automatically removes semicolon from generated content in such cases. 10464 * @param {IEmmetEditor} editor 10465 * @param {String} syntax 10466 * @param {String} profile 10467 */ 10468 ea.addHandler(function(editor, syntax, profile) { 10469 var cssSyntaxes = prefs.getArray('css.syntaxes'); 10470 if (!_.include(cssSyntaxes, syntax)) { 10471 return false; 10472 } 10473 10474 var caretPos = editor.getSelectionRange().end; 10475 var abbr = ea.findAbbreviation(editor); 10476 10477 if (abbr) { 10478 var content = emmet.expandAbbreviation(abbr, syntax, profile); 10479 if (content) { 10480 var replaceFrom = caretPos - abbr.length; 10481 var replaceTo = caretPos; 10482 if (editor.getContent().charAt(caretPos) == ';' && content.charAt(content.length - 1) == ';') { 10483 replaceTo++; 10484 } 10485 10486 editor.replaceContent(content, replaceFrom, replaceTo); 10487 return true; 10488 } 10489 } 10490 10491 return false; 10492 }); 10493 10494 return module = { 10495 /** 10496 * Adds vendor prefix 10497 * @param {String} name One-character prefix name 10498 * @param {Object} obj Object describing vendor prefix 10499 * @memberOf cssResolver 10500 */ 10501 addPrefix: addPrefix, 10502 10503 /** 10504 * Check if passed CSS property supports specified vendor prefix 10505 * @param {String} property 10506 * @param {String} prefix 10507 */ 10508 supportsPrefix: hasPrefix, 10509 10510 /** 10511 * Returns prefixed version of passed CSS property, only if this 10512 * property supports such prefix 10513 * @param {String} property 10514 * @param {String} prefix 10515 * @returns 10516 */ 10517 prefixed: function(property, prefix) { 10518 return hasPrefix(property, prefix) 10519 ? '-' + prefix + '-' + property 10520 : property; 10521 }, 10522 10523 /** 10524 * Returns list of all registered vendor prefixes 10525 * @returns {Array} 10526 */ 10527 listPrefixes: function() { 10528 return _.map(vendorPrefixes, function(obj) { 10529 return obj.prefix; 10530 }); 10531 }, 10532 10533 /** 10534 * Returns object describing vendor prefix 10535 * @param {String} name 10536 * @returns {Object} 10537 */ 10538 getPrefix: function(name) { 10539 return vendorPrefixes[name]; 10540 }, 10541 10542 /** 10543 * Removes prefix object 10544 * @param {String} name 10545 */ 10546 removePrefix: function(name) { 10547 if (name in vendorPrefixes) 10548 delete vendorPrefixes[name]; 10549 }, 10550 10551 /** 10552 * Extract vendor prefixes from abbreviation 10553 * @param {String} abbr 10554 * @returns {Object} Object containing array of prefixes and clean 10555 * abbreviation name 10556 */ 10557 extractPrefixes: function(abbr) { 10558 if (abbr.charAt(0) != '-') { 10559 return { 10560 property: abbr, 10561 prefixes: null 10562 }; 10563 } 10564 10565 // abbreviation may either contain sequence of one-character prefixes 10566 // or just dash, meaning that user wants to produce all possible 10567 // prefixed properties 10568 var i = 1, il = abbr.length, ch; 10569 var prefixes = []; 10570 10571 while (i < il) { 10572 ch = abbr.charAt(i); 10573 if (ch == '-') { 10574 // end-sequence character found, stop searching 10575 i++; 10576 break; 10577 } 10578 10579 if (ch in vendorPrefixes) { 10580 prefixes.push(ch); 10581 } else { 10582 // no prefix found, meaning user want to produce all 10583 // vendor-prefixed properties 10584 prefixes.length = 0; 10585 i = 1; 10586 break; 10587 } 10588 10589 i++; 10590 } 10591 10592 // reached end of abbreviation and no property name left 10593 if (i == il -1) { 10594 i = 1; 10595 prefixes.length = 1; 10596 } 10597 10598 return { 10599 property: abbr.substring(i), 10600 prefixes: prefixes.length ? prefixes : 'all' 10601 }; 10602 }, 10603 10604 /** 10605 * Search for value substring in abbreviation 10606 * @param {String} abbr 10607 * @returns {String} Value substring 10608 */ 10609 findValuesInAbbreviation: function(abbr, syntax) { 10610 syntax = syntax || 'css'; 10611 10612 var i = 0, il = abbr.length, value = '', ch; 10613 while (i < il) { 10614 ch = abbr.charAt(i); 10615 if (isNumeric(ch) || ch == '#' || (ch == '-' && isNumeric(abbr.charAt(i + 1)))) { 10616 value = abbr.substring(i); 10617 break; 10618 } 10619 10620 i++; 10621 } 10622 10623 // try to find keywords in abbreviation 10624 var property = abbr.substring(0, abbr.length - value.length); 10625 var res = require('resources'); 10626 var keywords = []; 10627 // try to extract some commonly-used properties 10628 while (~property.indexOf('-') && !res.findSnippet(syntax, property)) { 10629 var parts = property.split('-'); 10630 var lastPart = parts.pop(); 10631 if (!isValidKeyword(lastPart)) { 10632 break; 10633 } 10634 10635 keywords.unshift(lastPart); 10636 property = parts.join('-'); 10637 } 10638 10639 return keywords.join('-') + value; 10640 }, 10641 10642 parseValues: function(str) { 10643 /** @type StringStream */ 10644 var stream = require('stringStream').create(str); 10645 var values = []; 10646 var ch = null; 10647 10648 while ((ch = stream.next())) { 10649 if (ch == '#') { 10650 stream.match(/^t|[0-9a-f]+/i, true); 10651 values.push(stream.current()); 10652 } else if (ch == '-') { 10653 if (isValidKeyword(_.last(values)) || 10654 ( stream.start && isNumeric(str.charAt(stream.start - 1)) ) 10655 ) { 10656 stream.start = stream.pos; 10657 } 10658 10659 stream.match(/^\-?[0-9]*(\.[0-9]+)?[a-z%\.]*/, true); 10660 values.push(stream.current()); 10661 } else { 10662 stream.match(/^[0-9]*(\.[0-9]*)?[a-z%]*/, true); 10663 values.push(stream.current()); 10664 } 10665 10666 stream.start = stream.pos; 10667 } 10668 10669 return _.map(_.compact(values), normalizeValue); 10670 }, 10671 10672 /** 10673 * Extracts values from abbreviation 10674 * @param {String} abbr 10675 * @returns {Object} Object containing array of values and clean 10676 * abbreviation name 10677 */ 10678 extractValues: function(abbr) { 10679 // search for value start 10680 var abbrValues = this.findValuesInAbbreviation(abbr); 10681 if (!abbrValues) { 10682 return { 10683 property: abbr, 10684 values: null 10685 }; 10686 } 10687 10688 return { 10689 property: abbr.substring(0, abbr.length - abbrValues.length).replace(/-$/, ''), 10690 values: this.parseValues(abbrValues) 10691 }; 10692 }, 10693 10694 /** 10695 * Normalizes value, defined in abbreviation. 10696 * @param {String} value 10697 * @param {String} property 10698 * @returns {String} 10699 */ 10700 normalizeValue: function(value, property) { 10701 property = (property || '').toLowerCase(); 10702 var unitlessProps = prefs.getArray('css.unitlessProperties'); 10703 return value.replace(/^(\-?[0-9\.]+)([a-z]*)$/, function(str, val, unit) { 10704 if (!unit && (val == '0' || _.include(unitlessProps, property))) 10705 return val; 10706 10707 if (!unit) 10708 return val.replace(/\.$/, '') + prefs.get(~val.indexOf('.') ? 'css.floatUnit' : 'css.intUnit'); 10709 10710 return val + getUnit(unit); 10711 }); 10712 }, 10713 10714 /** 10715 * Expands abbreviation into a snippet 10716 * @param {String} abbr Abbreviation name to expand 10717 * @param {String} value Abbreviation value 10718 * @param {String} syntax Currect syntax or dialect. Default is 'css' 10719 * @returns {Object} Array of CSS properties and values or predefined 10720 * snippet (string or element) 10721 */ 10722 expand: function(abbr, value, syntax) { 10723 syntax = syntax || 'css'; 10724 var resources = require('resources'); 10725 var autoInsertPrefixes = prefs.get('css.autoInsertVendorPrefixes'); 10726 10727 // check if snippet should be transformed to !important 10728 var isImportant = /^(.+)\!$/.test(abbr); 10729 if (isImportant) { 10730 abbr = RegExp.$1; 10731 } 10732 10733 // check if we have abbreviated resource 10734 var snippet = resources.findSnippet(syntax, abbr); 10735 if (snippet && !autoInsertPrefixes) { 10736 return transformSnippet(snippet, isImportant, syntax); 10737 } 10738 10739 // no abbreviated resource, parse abbreviation 10740 var prefixData = this.extractPrefixes(abbr); 10741 var valuesData = this.extractValues(prefixData.property); 10742 var abbrData = _.extend(prefixData, valuesData); 10743 10744 if (!snippet) { 10745 snippet = resources.findSnippet(syntax, abbrData.property); 10746 } else { 10747 abbrData.values = null; 10748 } 10749 10750 if (!snippet && prefs.get('css.fuzzySearch')) { 10751 // let’s try fuzzy search 10752 snippet = resources.fuzzyFindSnippet(syntax, abbrData.property, parseFloat(prefs.get('css.fuzzySearchMinScore'))); 10753 } 10754 10755 if (!snippet) { 10756 snippet = abbrData.property + ':' + defaultValue; 10757 } else if (!_.isString(snippet)) { 10758 snippet = snippet.data; 10759 } 10760 10761 if (!isSingleProperty(snippet)) { 10762 return snippet; 10763 } 10764 10765 var snippetObj = this.splitSnippet(snippet); 10766 var result = []; 10767 if (!value && abbrData.values) { 10768 value = _.map(abbrData.values, function(val) { 10769 return this.normalizeValue(val, snippetObj.name); 10770 }, this).join(' ') + ';'; 10771 } 10772 10773 snippetObj.value = value || snippetObj.value; 10774 10775 var prefixes = abbrData.prefixes == 'all' || (!abbrData.prefixes && autoInsertPrefixes) 10776 ? findPrefixes(snippetObj.name, autoInsertPrefixes && abbrData.prefixes != 'all') 10777 : abbrData.prefixes; 10778 10779 10780 var names = [], propName; 10781 _.each(prefixes, function(p) { 10782 if (p in vendorPrefixes) { 10783 propName = vendorPrefixes[p].transformName(snippetObj.name); 10784 names.push(propName); 10785 result.push(transformSnippet(propName + ':' + snippetObj.value, 10786 isImportant, syntax)); 10787 } 10788 }); 10789 10790 // put the original property 10791 result.push(transformSnippet(snippetObj.name + ':' + snippetObj.value, isImportant, syntax)); 10792 names.push(snippetObj.name); 10793 10794 if (prefs.get('css.alignVendor')) { 10795 var pads = require('utils').getStringsPads(names); 10796 result = _.map(result, function(prop, i) { 10797 return pads[i] + prop; 10798 }); 10799 } 10800 10801 return result; 10802 }, 10803 10804 /** 10805 * Same as <code>expand</code> method but transforms output into 10806 * Emmet snippet 10807 * @param {String} abbr 10808 * @param {String} syntax 10809 * @returns {String} 10810 */ 10811 expandToSnippet: function(abbr, syntax) { 10812 var snippet = this.expand(abbr, null, syntax); 10813 if (_.isArray(snippet)) { 10814 return snippet.join('\n'); 10815 } 10816 10817 if (!_.isString(snippet)) 10818 return snippet.data; 10819 10820 return String(snippet); 10821 }, 10822 10823 /** 10824 * Split snippet into a CSS property-value pair 10825 * @param {String} snippet 10826 */ 10827 splitSnippet: function(snippet) { 10828 var utils = require('utils'); 10829 snippet = utils.trim(snippet); 10830 if (snippet.indexOf(':') == -1) { 10831 return { 10832 name: snippet, 10833 value: defaultValue 10834 }; 10835 } 10836 10837 var pair = snippet.split(':'); 10838 10839 return { 10840 name: utils.trim(pair.shift()), 10841 // replace ${0} tabstop to produce valid vendor-prefixed values 10842 // where possible 10843 value: utils.trim(pair.join(':')).replace(/^(\$\{0\}|\$0)(\s*;?)$/, '${1}$2') 10844 }; 10845 }, 10846 10847 getSyntaxPreference: getSyntaxPreference, 10848 transformSnippet: transformSnippet 10849 }; 10850 }); 10851 /** 10852 * 'Expand Abbreviation' handler that parses gradient definition from under 10853 * cursor and updates CSS rule with vendor-prefixed values. 10854 * 10855 * @memberOf __cssGradientHandlerDefine 10856 * @param {Function} require 10857 * @param {Underscore} _ 10858 */ 10859 emmet.define('cssGradient', function(require, _) { 10860 var defaultLinearDirections = ['top', 'to bottom', '0deg']; 10861 /** Back-reference to current module */ 10862 var module = null; 10863 10864 var cssSyntaxes = ['css', 'less', 'sass', 'scss', 'stylus', 'styl']; 10865 10866 var reDeg = /\d+deg/i; 10867 var reKeyword = /top|bottom|left|right/i; 10868 10869 // XXX define preferences 10870 /** @type preferences */ 10871 var prefs = require('preferences'); 10872 prefs.define('css.gradient.prefixes', 'webkit, moz, o', 10873 'A comma-separated list of vendor-prefixes for which values should ' 10874 + 'be generated.'); 10875 10876 prefs.define('css.gradient.oldWebkit', true, 10877 'Generate gradient definition for old Webkit implementations'); 10878 10879 prefs.define('css.gradient.omitDefaultDirection', true, 10880 'Do not output default direction definition in generated gradients.'); 10881 10882 prefs.define('css.gradient.defaultProperty', 'background-image', 10883 'When gradient expanded outside CSS value context, it will produce ' 10884 + 'properties with this name.'); 10885 10886 prefs.define('css.gradient.fallback', false, 10887 'With this option enabled, CSS gradient generator will produce ' 10888 + '<code>background-color</code> property with gradient first color ' 10889 + 'as fallback for old browsers.'); 10890 10891 function normalizeSpace(str) { 10892 return require('utils').trim(str).replace(/\s+/g, ' '); 10893 } 10894 10895 /** 10896 * Parses linear gradient definition 10897 * @param {String} 10898 */ 10899 function parseLinearGradient(gradient) { 10900 var direction = defaultLinearDirections[0]; 10901 10902 // extract tokens 10903 /** @type StringStream */ 10904 var stream = require('stringStream').create(require('utils').trim(gradient)); 10905 var colorStops = [], ch; 10906 while ((ch = stream.next())) { 10907 if (stream.peek() == ',') { 10908 colorStops.push(stream.current()); 10909 stream.next(); 10910 stream.eatSpace(); 10911 stream.start = stream.pos; 10912 } else if (ch == '(') { // color definition, like 'rgb(0,0,0)' 10913 stream.skipTo(')'); 10914 } 10915 } 10916 10917 // add last token 10918 colorStops.push(stream.current()); 10919 colorStops = _.compact(_.map(colorStops, normalizeSpace)); 10920 10921 if (!colorStops.length) 10922 return null; 10923 10924 // let's see if the first color stop is actually a direction 10925 if (reDeg.test(colorStops[0]) || reKeyword.test(colorStops[0])) { 10926 direction = colorStops.shift(); 10927 } 10928 10929 return { 10930 type: 'linear', 10931 direction: direction, 10932 colorStops: _.map(colorStops, parseColorStop) 10933 }; 10934 } 10935 10936 /** 10937 * Parses color stop definition 10938 * @param {String} colorStop 10939 * @returns {Object} 10940 */ 10941 function parseColorStop(colorStop) { 10942 colorStop = normalizeSpace(colorStop); 10943 10944 // find color declaration 10945 // first, try complex color declaration, like rgb(0,0,0) 10946 var color = null; 10947 colorStop = colorStop.replace(/^(\w+\(.+?\))\s*/, function(str, c) { 10948 color = c; 10949 return ''; 10950 }); 10951 10952 if (!color) { 10953 // try simple declaration, like yellow, #fco, #ffffff, etc. 10954 var parts = colorStop.split(' '); 10955 color = parts[0]; 10956 colorStop = parts[1] || ''; 10957 } 10958 10959 var result = { 10960 color: color 10961 }; 10962 10963 if (colorStop) { 10964 // there's position in color stop definition 10965 colorStop.replace(/^(\-?[\d\.]+)([a-z%]+)?$/, function(str, pos, unit) { 10966 result.position = pos; 10967 if (~pos.indexOf('.')) { 10968 unit = ''; 10969 } else if (!unit) { 10970 unit = '%'; 10971 } 10972 10973 if (unit) 10974 result.unit = unit; 10975 }); 10976 } 10977 10978 return result; 10979 } 10980 10981 /** 10982 * Resolves property name (abbreviation): searches for snippet definition in 10983 * 'resources' and returns new name of matched property 10984 */ 10985 function resolvePropertyName(name, syntax) { 10986 var res = require('resources'); 10987 var prefs = require('preferences'); 10988 var snippet = res.findSnippet(syntax, name); 10989 10990 if (!snippet && prefs.get('css.fuzzySearch')) { 10991 snippet = res.fuzzyFindSnippet(syntax, name, 10992 parseFloat(prefs.get('css.fuzzySearchMinScore'))); 10993 } 10994 10995 if (snippet) { 10996 if (!_.isString(snippet)) { 10997 snippet = snippet.data; 10998 } 10999 11000 return require('cssResolver').splitSnippet(snippet).name; 11001 } 11002 } 11003 11004 /** 11005 * Fills-out implied positions in color-stops. This function is useful for 11006 * old Webkit gradient definitions 11007 */ 11008 function fillImpliedPositions(colorStops) { 11009 var from = 0; 11010 11011 _.each(colorStops, function(cs, i) { 11012 // make sure that first and last positions are defined 11013 if (!i) 11014 return cs.position = cs.position || 0; 11015 11016 if (i == colorStops.length - 1 && !('position' in cs)) 11017 cs.position = 1; 11018 11019 if ('position' in cs) { 11020 var start = colorStops[from].position || 0; 11021 var step = (cs.position - start) / (i - from); 11022 _.each(colorStops.slice(from, i), function(cs2, j) { 11023 cs2.position = start + step * j; 11024 }); 11025 11026 from = i; 11027 } 11028 }); 11029 } 11030 11031 /** 11032 * Returns textual version of direction expressed in degrees 11033 * @param {String} direction 11034 * @returns {String} 11035 */ 11036 function textualDirection(direction) { 11037 var angle = parseFloat(direction); 11038 11039 if(!_.isNaN(angle)) { 11040 switch(angle % 360) { 11041 case 0: return 'left'; 11042 case 90: return 'bottom'; 11043 case 180: return 'right'; 11044 case 240: return 'top'; 11045 } 11046 } 11047 11048 return direction; 11049 } 11050 11051 /** 11052 * Creates direction definition for old Webkit gradients 11053 * @param {String} direction 11054 * @returns {String} 11055 */ 11056 function oldWebkitDirection(direction) { 11057 direction = textualDirection(direction); 11058 11059 if(reDeg.test(direction)) 11060 throw "The direction is an angle that can’t be converted."; 11061 11062 var v = function(pos) { 11063 return ~direction.indexOf(pos) ? '100%' : '0'; 11064 }; 11065 11066 return v('right') + ' ' + v('bottom') + ', ' + v('left') + ' ' + v('top'); 11067 } 11068 11069 function getPrefixedNames(name) { 11070 var prefixes = prefs.getArray('css.gradient.prefixes'); 11071 var names = prefixes 11072 ? _.map(prefixes, function(p) { 11073 return '-' + p + '-' + name; 11074 }) 11075 : []; 11076 11077 names.push(name); 11078 11079 return names; 11080 } 11081 11082 /** 11083 * Returns list of CSS properties with gradient 11084 * @param {Object} gradient 11085 * @param {String} propertyName Original CSS property name 11086 * @returns {Array} 11087 */ 11088 function getPropertiesForGradient(gradient, propertyName) { 11089 var props = []; 11090 var css = require('cssResolver'); 11091 11092 if (prefs.get('css.gradient.fallback') && ~propertyName.toLowerCase().indexOf('background')) { 11093 props.push({ 11094 name: 'background-color', 11095 value: '${1:' + gradient.colorStops[0].color + '}' 11096 }); 11097 } 11098 11099 _.each(prefs.getArray('css.gradient.prefixes'), function(prefix) { 11100 var name = css.prefixed(propertyName, prefix); 11101 if (prefix == 'webkit' && prefs.get('css.gradient.oldWebkit')) { 11102 try { 11103 props.push({ 11104 name: name, 11105 value: module.oldWebkitLinearGradient(gradient) 11106 }); 11107 } catch(e) {} 11108 } 11109 11110 props.push({ 11111 name: name, 11112 value: module.toString(gradient, prefix) 11113 }); 11114 }); 11115 11116 return props.sort(function(a, b) { 11117 return b.name.length - a.name.length; 11118 }); 11119 } 11120 11121 /** 11122 * Pastes gradient definition into CSS rule with correct vendor-prefixes 11123 * @param {EditElement} property Matched CSS property 11124 * @param {Object} gradient Parsed gradient 11125 * @param {Range} valueRange If passed, only this range within property 11126 * value will be replaced with gradient. Otherwise, full value will be 11127 * replaced 11128 */ 11129 function pasteGradient(property, gradient, valueRange) { 11130 var rule = property.parent; 11131 var utils = require('utils'); 11132 var alignVendor = require('preferences').get('css.alignVendor'); 11133 11134 // we may have aligned gradient definitions: find the smallest value 11135 // separator 11136 var sep = property.styleSeparator; 11137 var before = property.styleBefore; 11138 11139 // first, remove all properties within CSS rule with the same name and 11140 // gradient definition 11141 _.each(rule.getAll(getPrefixedNames(property.name())), function(item) { 11142 if (item != property && /gradient/i.test(item.value())) { 11143 if (item.styleSeparator.length < sep.length) { 11144 sep = item.styleSeparator; 11145 } 11146 if (item.styleBefore.length < before.length) { 11147 before = item.styleBefore; 11148 } 11149 rule.remove(item); 11150 } 11151 }); 11152 11153 if (alignVendor) { 11154 // update prefix 11155 if (before != property.styleBefore) { 11156 var fullRange = property.fullRange(); 11157 rule._updateSource(before, fullRange.start, fullRange.start + property.styleBefore.length); 11158 property.styleBefore = before; 11159 } 11160 11161 // update separator value 11162 if (sep != property.styleSeparator) { 11163 rule._updateSource(sep, property.nameRange().end, property.valueRange().start); 11164 property.styleSeparator = sep; 11165 } 11166 } 11167 11168 var value = property.value(); 11169 if (!valueRange) 11170 valueRange = require('range').create(0, property.value()); 11171 11172 var val = function(v) { 11173 return utils.replaceSubstring(value, v, valueRange); 11174 }; 11175 11176 // put vanilla-clean gradient definition into current rule 11177 property.value(val(module.toString(gradient)) + '${2}'); 11178 11179 // create list of properties to insert 11180 var propsToInsert = getPropertiesForGradient(gradient, property.name()); 11181 11182 // align prefixed values 11183 if (alignVendor) { 11184 var values = _.pluck(propsToInsert, 'value'); 11185 var names = _.pluck(propsToInsert, 'name'); 11186 values.push(property.value()); 11187 names.push(property.name()); 11188 11189 var valuePads = utils.getStringsPads(_.map(values, function(v) { 11190 return v.substring(0, v.indexOf('(')); 11191 })); 11192 11193 var namePads = utils.getStringsPads(names); 11194 property.name(_.last(namePads) + property.name()); 11195 11196 _.each(propsToInsert, function(prop, i) { 11197 prop.name = namePads[i] + prop.name; 11198 prop.value = valuePads[i] + prop.value; 11199 }); 11200 11201 property.value(_.last(valuePads) + property.value()); 11202 } 11203 11204 // put vendor-prefixed definitions before current rule 11205 _.each(propsToInsert, function(prop) { 11206 rule.add(prop.name, prop.value, rule.indexOf(property)); 11207 }); 11208 } 11209 11210 /** 11211 * Search for gradient definition inside CSS property value 11212 */ 11213 function findGradient(cssProp) { 11214 var value = cssProp.value(); 11215 var gradient = null; 11216 var matchedPart = _.find(cssProp.valueParts(), function(part) { 11217 return gradient = module.parse(part.substring(value)); 11218 }); 11219 11220 if (matchedPart && gradient) { 11221 return { 11222 gradient: gradient, 11223 valueRange: matchedPart 11224 }; 11225 } 11226 11227 return null; 11228 } 11229 11230 /** 11231 * Tries to expand gradient outside CSS value 11232 * @param {IEmmetEditor} editor 11233 * @param {String} syntax 11234 */ 11235 function expandGradientOutsideValue(editor, syntax) { 11236 var propertyName = prefs.get('css.gradient.defaultProperty'); 11237 11238 if (!propertyName) 11239 return false; 11240 11241 // assuming that gradient definition is written on new line, 11242 // do a simplified parsing 11243 var content = String(editor.getContent()); 11244 /** @type Range */ 11245 var lineRange = require('range').create(editor.getCurrentLineRange()); 11246 11247 // get line content and adjust range with padding 11248 var line = lineRange.substring(content) 11249 .replace(/^\s+/, function(pad) { 11250 lineRange.start += pad.length; 11251 return ''; 11252 }) 11253 .replace(/\s+$/, function(pad) { 11254 lineRange.end -= pad.length; 11255 return ''; 11256 }); 11257 11258 var css = require('cssResolver'); 11259 var gradient = module.parse(line); 11260 if (gradient) { 11261 var props = getPropertiesForGradient(gradient, propertyName); 11262 props.push({ 11263 name: propertyName, 11264 value: module.toString(gradient) + '${2}' 11265 }); 11266 11267 var sep = css.getSyntaxPreference('valueSeparator', syntax); 11268 var end = css.getSyntaxPreference('propertyEnd', syntax); 11269 11270 if (require('preferences').get('css.alignVendor')) { 11271 var pads = require('utils').getStringsPads(_.map(props, function(prop) { 11272 return prop.value.substring(0, prop.value.indexOf('(')); 11273 })); 11274 _.each(props, function(prop, i) { 11275 prop.value = pads[i] + prop.value; 11276 }); 11277 } 11278 11279 props = _.map(props, function(item) { 11280 return item.name + sep + item.value + end; 11281 }); 11282 11283 editor.replaceContent(props.join('\n'), lineRange.start, lineRange.end); 11284 return true; 11285 } 11286 11287 return false; 11288 } 11289 11290 /** 11291 * Search for gradient definition inside CSS value under cursor 11292 * @param {String} content 11293 * @param {Number} pos 11294 * @returns {Object} 11295 */ 11296 function findGradientFromPosition(content, pos) { 11297 var cssProp = null; 11298 /** @type EditContainer */ 11299 var cssRule = require('cssEditTree').parseFromPosition(content, pos, true); 11300 11301 if (cssRule) { 11302 cssProp = cssRule.itemFromPosition(pos, true); 11303 if (!cssProp) { 11304 // in case user just started writing CSS property 11305 // and didn't include semicolon–try another approach 11306 cssProp = _.find(cssRule.list(), function(elem) { 11307 return elem.range(true).end == pos; 11308 }); 11309 } 11310 } 11311 11312 return { 11313 rule: cssRule, 11314 property: cssProp 11315 }; 11316 } 11317 11318 // XXX register expand abbreviation handler 11319 /** 11320 * @param {IEmmetEditor} editor 11321 * @param {String} syntax 11322 * @param {String} profile 11323 */ 11324 require('expandAbbreviation').addHandler(function(editor, syntax, profile) { 11325 var info = require('editorUtils').outputInfo(editor, syntax, profile); 11326 if (!_.include(cssSyntaxes, info.syntax)) 11327 return false; 11328 11329 // let's see if we are expanding gradient definition 11330 var caret = editor.getCaretPos(); 11331 var content = info.content; 11332 var css = findGradientFromPosition(content, caret); 11333 11334 if (css.property) { 11335 // make sure that caret is inside property value with gradient 11336 // definition 11337 var g = findGradient(css.property); 11338 if (g) { 11339 var ruleStart = css.rule.options.offset || 0; 11340 var ruleEnd = ruleStart + css.rule.toString().length; 11341 11342 // Handle special case: 11343 // user wrote gradient definition between existing CSS 11344 // properties and did not finished it with semicolon. 11345 // In this case, we have semicolon right after gradient 11346 // definition and re-parse rule again 11347 if (/[\n\r]/.test(css.property.value())) { 11348 // insert semicolon at the end of gradient definition 11349 var insertPos = css.property.valueRange(true).start + g.valueRange.end; 11350 content = require('utils').replaceSubstring(content, ';', insertPos); 11351 var newCss = findGradientFromPosition(content, caret); 11352 if (newCss.property) { 11353 g = findGradient(newCss.property); 11354 css = newCss; 11355 } 11356 } 11357 11358 // make sure current property has terminating semicolon 11359 css.property.end(';'); 11360 11361 // resolve CSS property name 11362 var resolvedName = resolvePropertyName(css.property.name(), syntax); 11363 if (resolvedName) { 11364 css.property.name(resolvedName); 11365 } 11366 11367 pasteGradient(css.property, g.gradient, g.valueRange); 11368 editor.replaceContent(css.rule.toString(), ruleStart, ruleEnd, true); 11369 return true; 11370 } 11371 } 11372 11373 return expandGradientOutsideValue(editor, syntax); 11374 }); 11375 11376 // XXX register "Reflect CSS Value" action delegate 11377 /** 11378 * @param {EditElement} property 11379 */ 11380 require('reflectCSSValue').addHandler(function(property) { 11381 var utils = require('utils'); 11382 11383 var g = findGradient(property); 11384 if (!g) 11385 return false; 11386 11387 var value = property.value(); 11388 var val = function(v) { 11389 return utils.replaceSubstring(value, v, g.valueRange); 11390 }; 11391 11392 // reflect value for properties with the same name 11393 _.each(property.parent.getAll(getPrefixedNames(property.name())), function(prop) { 11394 if (prop === property) 11395 return; 11396 11397 // check if property value starts with gradient definition 11398 var m = prop.value().match(/^\s*(\-([a-z]+)\-)?linear\-gradient/); 11399 if (m) { 11400 prop.value(val(module.toString(g.gradient, m[2] || ''))); 11401 } else if ((m = prop.value().match(/\s*\-webkit\-gradient/))) { 11402 // old webkit gradient definition 11403 prop.value(val(module.oldWebkitLinearGradient(g.gradient))); 11404 } 11405 }); 11406 11407 return true; 11408 }); 11409 11410 return module = { 11411 /** 11412 * Parses gradient definition 11413 * @param {String} gradient 11414 * @returns {Object} 11415 */ 11416 parse: function(gradient) { 11417 var result = null; 11418 require('utils').trim(gradient).replace(/^([\w\-]+)\((.+?)\)$/, function(str, type, definition) { 11419 // remove vendor prefix 11420 type = type.toLowerCase().replace(/^\-[a-z]+\-/, ''); 11421 if (type == 'linear-gradient' || type == 'lg') { 11422 result = parseLinearGradient(definition); 11423 return ''; 11424 } 11425 11426 return str; 11427 }); 11428 11429 return result; 11430 }, 11431 11432 /** 11433 * Produces linear gradient definition used in early Webkit 11434 * implementations 11435 * @param {Object} gradient Parsed gradient 11436 * @returns {String} 11437 */ 11438 oldWebkitLinearGradient: function(gradient) { 11439 if (_.isString(gradient)) 11440 gradient = this.parse(gradient); 11441 11442 if (!gradient) 11443 return null; 11444 11445 var colorStops = _.map(gradient.colorStops, _.clone); 11446 11447 // normalize color-stops position 11448 _.each(colorStops, function(cs) { 11449 if (!('position' in cs)) // implied position 11450 return; 11451 11452 if (~cs.position.indexOf('.') || cs.unit == '%') { 11453 cs.position = parseFloat(cs.position) / (cs.unit == '%' ? 100 : 1); 11454 } else { 11455 throw "Can't convert color stop '" + (cs.position + (cs.unit || '')) + "'"; 11456 } 11457 }); 11458 11459 fillImpliedPositions(colorStops); 11460 11461 // transform color-stops into string representation 11462 colorStops = _.map(colorStops, function(cs, i) { 11463 if (!cs.position && !i) 11464 return 'from(' + cs.color + ')'; 11465 11466 if (cs.position == 1 && i == colorStops.length - 1) 11467 return 'to(' + cs.color + ')'; 11468 11469 return 'color-stop(' + (cs.position.toFixed(2).replace(/\.?0+$/, '')) + ', ' + cs.color + ')'; 11470 }); 11471 11472 return '-webkit-gradient(linear, ' 11473 + oldWebkitDirection(gradient.direction) 11474 + ', ' 11475 + colorStops.join(', ') 11476 + ')'; 11477 }, 11478 11479 /** 11480 * Returns string representation of parsed gradient 11481 * @param {Object} gradient Parsed gradient 11482 * @param {String} prefix Vendor prefix 11483 * @returns {String} 11484 */ 11485 toString: function(gradient, prefix) { 11486 if (gradient.type == 'linear') { 11487 var fn = (prefix ? '-' + prefix + '-' : '') + 'linear-gradient'; 11488 11489 // transform color-stops 11490 var colorStops = _.map(gradient.colorStops, function(cs) { 11491 return cs.color + ('position' in cs 11492 ? ' ' + cs.position + (cs.unit || '') 11493 : ''); 11494 }); 11495 11496 if (gradient.direction 11497 && (!prefs.get('css.gradient.omitDefaultDirection') 11498 || !_.include(defaultLinearDirections, gradient.direction))) { 11499 colorStops.unshift(gradient.direction); 11500 } 11501 11502 return fn + '(' + colorStops.join(', ') + ')'; 11503 } 11504 } 11505 }; 11506 });/** 11507 * Module adds support for generators: a regexp-based abbreviation resolver 11508 * that can produce custom output. 11509 * @param {Function} require 11510 * @param {Underscore} _ 11511 */ 11512 emmet.exec(function(require, _) { 11513 /** @type HandlerList */ 11514 var generators = require('handlerList').create(); 11515 var resources = require('resources'); 11516 11517 _.extend(resources, { 11518 /** 11519 * Add generator. A generator function <code>fn</code> will be called 11520 * only if current abbreviation matches <code>regexp</code> regular 11521 * expression and this function should return <code>null</code> if 11522 * abbreviation cannot be resolved 11523 * @param {RegExp} regexp Regular expression for abbreviation element name 11524 * @param {Function} fn Resolver function 11525 * @param {Object} options Options list as described in 11526 * {@link HandlerList#add()} method 11527 */ 11528 addGenerator: function(regexp, fn, options) { 11529 if (_.isString(regexp)) 11530 regexp = new RegExp(regexp); 11531 11532 generators.add(function(node, syntax) { 11533 var m; 11534 if ((m = regexp.exec(node.name()))) { 11535 return fn(m, node, syntax); 11536 } 11537 11538 return null; 11539 }, options); 11540 } 11541 }); 11542 11543 resources.addResolver(function() { 11544 return generators.exec(null, _.toArray(arguments)); 11545 }); 11546 });/** 11547 * Module for resolving tag names: returns best matched tag name for child 11548 * element based on passed parent's tag name. Also provides utility function 11549 * for element type detection (inline, block-level, empty) 11550 * @param {Function} require 11551 * @param {Underscore} _ 11552 */ 11553 emmet.define('tagName', function(require, _) { 11554 var elementTypes = { 11555 // empty: 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command'.split(','), 11556 empty: [], 11557 blockLevel: 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6'.split(','), 11558 inlineLevel: 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'.split(',') 11559 }; 11560 11561 var elementMap = { 11562 'p': 'span', 11563 'ul': 'li', 11564 'ol': 'li', 11565 'table': 'tr', 11566 'tr': 'td', 11567 'tbody': 'tr', 11568 'thead': 'tr', 11569 'tfoot': 'tr', 11570 'colgroup': 'col', 11571 'select': 'option', 11572 'optgroup': 'option', 11573 'audio': 'source', 11574 'video': 'source', 11575 'object': 'param', 11576 'map': 'area' 11577 }; 11578 11579 return { 11580 /** 11581 * Returns best matched child element name for passed parent's 11582 * tag name 11583 * @param {String} name 11584 * @returns {String} 11585 * @memberOf tagName 11586 */ 11587 resolve: function(name) { 11588 name = (name || '').toLowerCase(); 11589 11590 if (name in elementMap) 11591 return this.getMapping(name); 11592 11593 if (this.isInlineLevel(name)) 11594 return 'span'; 11595 11596 return 'div'; 11597 }, 11598 11599 /** 11600 * Returns mapped child element name for passed parent's name 11601 * @param {String} name 11602 * @returns {String} 11603 */ 11604 getMapping: function(name) { 11605 return elementMap[name.toLowerCase()]; 11606 }, 11607 11608 /** 11609 * Check if passed element name belongs to inline-level element 11610 * @param {String} name 11611 * @returns {Boolean} 11612 */ 11613 isInlineLevel: function(name) { 11614 return this.isTypeOf(name, 'inlineLevel'); 11615 }, 11616 11617 /** 11618 * Check if passed element belongs to block-level element. 11619 * For better matching of unknown elements (for XML, for example), 11620 * you should use <code>!this.isInlineLevel(name)</code> 11621 * @returns {Boolean} 11622 */ 11623 isBlockLevel: function(name) { 11624 return this.isTypeOf(name, 'blockLevel'); 11625 }, 11626 11627 /** 11628 * Check if passed element is void (i.e. should not have closing tag). 11629 * @returns {Boolean} 11630 */ 11631 isEmptyElement: function(name) { 11632 return this.isTypeOf(name, 'empty'); 11633 }, 11634 11635 /** 11636 * Generic function for testing if element name belongs to specified 11637 * elements collection 11638 * @param {String} name Element name 11639 * @param {String} type Collection name 11640 * @returns {Boolean} 11641 */ 11642 isTypeOf: function(name, type) { 11643 return _.include(elementTypes[type], name); 11644 }, 11645 11646 /** 11647 * Adds new parent–child mapping 11648 * @param {String} parent 11649 * @param {String} child 11650 */ 11651 addMapping: function(parent, child) { 11652 elementMap[parent] = child; 11653 }, 11654 11655 /** 11656 * Removes parent-child mapping 11657 */ 11658 removeMapping: function(parent) { 11659 if (parent in elementMap) 11660 delete elementMap[parent]; 11661 }, 11662 11663 /** 11664 * Adds new element into collection 11665 * @param {String} name Element name 11666 * @param {String} collection Collection name 11667 */ 11668 addElementToCollection: function(name, collection) { 11669 if (!elementTypes[collection]) 11670 elementTypes[collection] = []; 11671 11672 var col = this.getCollection(collection); 11673 if (!_.include(col, name)) 11674 col.push(name); 11675 }, 11676 11677 /** 11678 * Removes element name from specified collection 11679 * @param {String} name Element name 11680 * @param {String} collection Collection name 11681 * @returns 11682 */ 11683 removeElementFromCollection: function(name, collection) { 11684 if (collection in elementTypes) { 11685 elementTypes[collection] = _.without(this.getCollection(collection), name); 11686 } 11687 }, 11688 11689 /** 11690 * Returns elements name collection 11691 * @param {String} name Collection name 11692 * @returns {Array} 11693 */ 11694 getCollection: function(name) { 11695 return elementTypes[name]; 11696 } 11697 }; 11698 });/** 11699 * Filter for aiding of writing elements with complex class names as described 11700 * in Yandex's BEM (Block, Element, Modifier) methodology. This filter will 11701 * automatically inherit block and element names from parent elements and insert 11702 * them into child element classes 11703 * @memberOf __bemFilterDefine 11704 * @constructor 11705 * @param {Function} require 11706 * @param {Underscore} _ 11707 */ 11708 emmet.exec(function(require, _) { 11709 var prefs = require('preferences'); 11710 prefs.define('bem.elementSeparator', '__', 'Class name’s element separator.'); 11711 prefs.define('bem.modifierSeparator', '_', 'Class name’s modifier separator.'); 11712 prefs.define('bem.shortElementPrefix', '-', 11713 'Symbol for describing short “block-element” notation. Class names ' 11714 + 'prefixed with this symbol will be treated as element name for parent‘s ' 11715 + 'block name. Each symbol instance traverses one level up in parsed ' 11716 + 'tree for block name lookup. Empty value will disable short notation.'); 11717 11718 var shouldRunHtmlFilter = false; 11719 11720 function getSeparators() { 11721 return { 11722 element: prefs.get('bem.elementSeparator'), 11723 modifier: prefs.get('bem.modifierSeparator') 11724 }; 11725 } 11726 11727 /** 11728 * @param {AbbreviationNode} item 11729 */ 11730 function bemParse(item) { 11731 if (require('abbreviationUtils').isSnippet(item)) 11732 return item; 11733 11734 // save BEM stuff in cache for faster lookups 11735 item.__bem = { 11736 block: '', 11737 element: '', 11738 modifier: '' 11739 }; 11740 11741 var classNames = normalizeClassName(item.attribute('class')).split(' '); 11742 11743 // guess best match for block name 11744 var reBlockName = /^[a-z]\-/i; 11745 item.__bem.block = _.find(classNames, function(name) { 11746 return reBlockName.test(name); 11747 }); 11748 11749 // guessing doesn't worked, pick first class name as block name 11750 if (!item.__bem.block) { 11751 reBlockName = /^[a-z]/i; 11752 item.__bem.block = _.find(classNames, function(name) { 11753 return reBlockName.test(name); 11754 }) || ''; 11755 } 11756 11757 classNames = _.chain(classNames) 11758 .map(function(name) {return processClassName(name, item);}) 11759 .flatten() 11760 .uniq() 11761 .value() 11762 .join(' '); 11763 11764 if (classNames) 11765 item.attribute('class', classNames); 11766 11767 return item; 11768 } 11769 11770 /** 11771 * @param {String} className 11772 * @returns {String} 11773 */ 11774 function normalizeClassName(className) { 11775 var utils = require('utils'); 11776 className = (' ' + (className || '') + ' ').replace(/\s+/g, ' '); 11777 11778 var shortSymbol = prefs.get('bem.shortElementPrefix'); 11779 if (shortSymbol) { 11780 var re = new RegExp('\\s(' + utils.escapeForRegexp(shortSymbol) + '+)', 'g'); 11781 className = className.replace(re, function(str, p1) { 11782 return ' ' + utils.repeatString(getSeparators().element, p1.length); 11783 }); 11784 } 11785 11786 return utils.trim(className); 11787 } 11788 11789 /** 11790 * Processes class name 11791 * @param {String} name Class name item to process 11792 * @param {AbbreviationNode} item Host node for provided class name 11793 * @returns Processed class name. May return <code>Array</code> of 11794 * class names 11795 */ 11796 function processClassName(name, item) { 11797 name = transformClassName(name, item, 'element'); 11798 name = transformClassName(name, item, 'modifier'); 11799 11800 // expand class name 11801 // possible values: 11802 // * block__element 11803 // * block__element_modifier 11804 // * block__element_modifier1_modifier2 11805 // * block_modifier 11806 var block = '', element = '', modifier = ''; 11807 var separators = getSeparators(); 11808 if (~name.indexOf(separators.element)) { 11809 var blockElem = name.split(separators.element); 11810 var elemModifiers = blockElem[1].split(separators.modifier); 11811 11812 block = blockElem[0]; 11813 element = elemModifiers.shift(); 11814 modifier = elemModifiers.join(separators.modifier); 11815 } else if (~name.indexOf(separators.modifier)) { 11816 var blockModifiers = name.split(separators.modifier); 11817 11818 block = blockModifiers.shift(); 11819 modifier = blockModifiers.join(separators.modifier); 11820 } 11821 11822 if (block || element || modifier) { 11823 if (!block) { 11824 block = item.__bem.block; 11825 } 11826 11827 // inherit parent bem element, if exists 11828 // if (item.parent && item.parent.__bem && item.parent.__bem.element) 11829 // element = item.parent.__bem.element + separators.element + element; 11830 11831 // produce multiple classes 11832 var prefix = block; 11833 var result = []; 11834 11835 if (element) { 11836 prefix += separators.element + element; 11837 result.push(prefix); 11838 } else { 11839 result.push(prefix); 11840 } 11841 11842 if (modifier) { 11843 result.push(prefix + separators.modifier + modifier); 11844 } 11845 11846 item.__bem.block = block; 11847 item.__bem.element = element; 11848 item.__bem.modifier = modifier; 11849 11850 return result; 11851 } 11852 11853 // ...otherwise, return processed or original class name 11854 return name; 11855 } 11856 11857 /** 11858 * Low-level function to transform user-typed class name into full BEM class 11859 * @param {String} name Class name item to process 11860 * @param {AbbreviationNode} item Host node for provided class name 11861 * @param {String} entityType Type of entity to be tried to transform 11862 * ('element' or 'modifier') 11863 * @returns {String} Processed class name or original one if it can't be 11864 * transformed 11865 */ 11866 function transformClassName(name, item, entityType) { 11867 var separators = getSeparators(); 11868 var reSep = new RegExp('^(' + separators[entityType] + ')+', 'g'); 11869 if (reSep.test(name)) { 11870 var depth = 0; // parent lookup depth 11871 var cleanName = name.replace(reSep, function(str) { 11872 depth = str.length / separators[entityType].length; 11873 return ''; 11874 }); 11875 11876 // find donor element 11877 var donor = item; 11878 while (donor.parent && depth--) { 11879 donor = donor.parent; 11880 } 11881 11882 if (!donor || !donor.__bem) 11883 donor = item; 11884 11885 if (donor && donor.__bem) { 11886 var prefix = donor.__bem.block; 11887 11888 // decide if we should inherit element name 11889 // if (entityType == 'element') { 11890 // var curElem = cleanName.split(separators.modifier, 1)[0]; 11891 // if (donor.__bem.element && donor.__bem.element != curElem) 11892 // prefix += separators.element + donor.__bem.element; 11893 // } 11894 11895 if (entityType == 'modifier' && donor.__bem.element) 11896 prefix += separators.element + donor.__bem.element; 11897 11898 return prefix + separators[entityType] + cleanName; 11899 } 11900 } 11901 11902 return name; 11903 } 11904 11905 /** 11906 * Recursive function for processing tags, which extends class names 11907 * according to BEM specs: http://bem.github.com/bem-method/pages/beginning/beginning.ru.html 11908 * <br><br> 11909 * It does several things:<br> 11910 * <ul> 11911 * <li>Expands complex class name (according to BEM symbol semantics): 11912 * .block__elem_modifier → .block.block__elem.block__elem_modifier 11913 * </li> 11914 * <li>Inherits block name on child elements: 11915 * .b-block > .__el > .__el → .b-block > .b-block__el > .b-block__el__el 11916 * </li> 11917 * <li>Treats first dash symbol as '__'</li> 11918 * <li>Double underscore (or typographic '–') is also treated as an element 11919 * level lookup, e.g. ____el will search for element definition in parent’s 11920 * parent element: 11921 * .b-block > .__el1 > .____el2 → .b-block > .b-block__el1 > .b-block__el2 11922 * </li> 11923 * </ul> 11924 * 11925 * @param {AbbreviationNode} tree 11926 * @param {Object} profile 11927 */ 11928 function process(tree, profile) { 11929 if (tree.name) 11930 bemParse(tree, profile); 11931 11932 var abbrUtils = require('abbreviationUtils'); 11933 _.each(tree.children, function(item) { 11934 process(item, profile); 11935 if (!abbrUtils.isSnippet(item) && item.start) 11936 shouldRunHtmlFilter = true; 11937 }); 11938 11939 return tree; 11940 } 11941 11942 require('filters').add('bem', function(tree, profile) { 11943 shouldRunHtmlFilter = false; 11944 tree = process(tree, profile); 11945 // in case 'bem' filter is applied after 'html' filter: run it again 11946 // to update output 11947 if (shouldRunHtmlFilter) { 11948 tree = require('filters').apply(tree, 'html', profile); 11949 } 11950 11951 return tree; 11952 }); 11953 }); 11954 11955 /** 11956 * Comment important tags (with 'id' and 'class' attributes) 11957 * @author Sergey Chikuyonok (serge.che@gmail.com) 11958 * @link http://chikuyonok.ru 11959 * @constructor 11960 * @memberOf __commentFilterDefine 11961 * @param {Function} require 11962 * @param {Underscore} _ 11963 */ 11964 emmet.exec(function(require, _) { 11965 // define some preferences 11966 /** @type emmet.preferences */ 11967 var prefs = require('preferences'); 11968 11969 prefs.define('filter.commentAfter', 11970 '\n<!-- /<%= attr("id", "#") %><%= attr("class", ".") %> -->', 11971 'A definition of comment that should be placed <i>after</i> matched ' 11972 + 'element when <code>comment</code> filter is applied. This definition ' 11973 + 'is an ERB-style template passed to <code>_.template()</code> ' 11974 + 'function (see Underscore.js docs for details). In template context, ' 11975 + 'the following properties and functions are availabe:\n' 11976 + '<ul>' 11977 11978 + '<li><code>attr(name, before, after)</code> – a function that outputs' 11979 + 'specified attribute value concatenated with <code>before</code> ' 11980 + 'and <code>after</code> strings. If attribute doesn\'t exists, the ' 11981 + 'empty string will be returned.</li>' 11982 11983 + '<li><code>node</code> – current node (instance of <code>AbbreviationNode</code>)</li>' 11984 11985 + '<li><code>name</code> – name of current tag</li>' 11986 11987 + '<li><code>padding</code> – current string padding, can be used ' 11988 + 'for formatting</li>' 11989 11990 +'</ul>'); 11991 11992 prefs.define('filter.commentBefore', 11993 '', 11994 'A definition of comment that should be placed <i>before</i> matched ' 11995 + 'element when <code>comment</code> filter is applied. ' 11996 + 'For more info, read description of <code>filter.commentAfter</code> ' 11997 + 'property'); 11998 11999 prefs.define('filter.commentTrigger', 'id, class', 12000 'A comma-separated list of attribute names that should exist in abbreviatoin ' 12001 + 'where comment should be added. If you wish to add comment for ' 12002 + 'every element, set this option to <code>*</code>'); 12003 12004 /** 12005 * Add comments to tag 12006 * @param {AbbreviationNode} node 12007 */ 12008 function addComments(node, templateBefore, templateAfter) { 12009 var utils = require('utils'); 12010 12011 // check if comments should be added 12012 var trigger = prefs.get('filter.commentTrigger'); 12013 if (trigger != '*') { 12014 var shouldAdd = _.find(trigger.split(','), function(name) { 12015 return !!node.attribute(utils.trim(name)); 12016 }); 12017 if (!shouldAdd) return; 12018 } 12019 12020 var ctx = { 12021 node: node, 12022 name: node.name(), 12023 padding: node.parent ? node.parent.padding : '', 12024 attr: function(name, before, after) { 12025 var attr = node.attribute(name); 12026 if (attr) { 12027 return (before || '') + attr + (after || ''); 12028 } 12029 12030 return ''; 12031 } 12032 }; 12033 12034 var nodeBefore = utils.normalizeNewline(templateBefore ? templateBefore(ctx) : ''); 12035 var nodeAfter = utils.normalizeNewline(templateAfter ? templateAfter(ctx) : ''); 12036 12037 node.start = node.start.replace(/</, nodeBefore + '<'); 12038 node.end = node.end.replace(/>/, '>' + nodeAfter); 12039 } 12040 12041 function process(tree, before, after) { 12042 var abbrUtils = require('abbreviationUtils'); 12043 _.each(tree.children, function(item) { 12044 if (abbrUtils.isBlock(item)) 12045 addComments(item, before, after); 12046 12047 process(item, before, after); 12048 }); 12049 12050 return tree; 12051 } 12052 12053 require('filters').add('c', function(tree) { 12054 var templateBefore = _.template(prefs.get('filter.commentBefore')); 12055 var templateAfter = _.template(prefs.get('filter.commentAfter')); 12056 12057 return process(tree, templateBefore, templateAfter); 12058 }); 12059 }); 12060 /** 12061 * Filter for escaping unsafe XML characters: <, >, & 12062 * @author Sergey Chikuyonok (serge.che@gmail.com) 12063 * @link http://chikuyonok.ru 12064 */ 12065 emmet.exec(function(require, _) { 12066 var charMap = { 12067 '<': '<', 12068 '>': '>', 12069 '&': '&' 12070 }; 12071 12072 function escapeChars(str) { 12073 return str.replace(/([<>&])/g, function(str, p1){ 12074 return charMap[p1]; 12075 }); 12076 } 12077 12078 require('filters').add('e', function process(tree) { 12079 _.each(tree.children, function(item) { 12080 item.start = escapeChars(item.start); 12081 item.end = escapeChars(item.end); 12082 item.content = escapeChars(item.content); 12083 process(item); 12084 }); 12085 12086 return tree; 12087 }); 12088 });/** 12089 * Generic formatting filter: creates proper indentation for each tree node, 12090 * placing "%s" placeholder where the actual output should be. You can use 12091 * this filter to preformat tree and then replace %s placeholder to whatever you 12092 * need. This filter should't be called directly from editor as a part 12093 * of abbreviation. 12094 * @author Sergey Chikuyonok (serge.che@gmail.com) 12095 * @link http://chikuyonok.ru 12096 * @constructor 12097 * @memberOf __formatFilterDefine 12098 * @param {Function} require 12099 * @param {Underscore} _ 12100 */ 12101 emmet.exec(function(require, _){ 12102 var placeholder = '%s'; 12103 12104 /** @type preferences */ 12105 var prefs = require('preferences'); 12106 prefs.define('format.noIndentTags', 'html', 12107 'A comma-separated list of tag names that should not get inner indentation.'); 12108 12109 prefs.define('format.forceIndentationForTags', 'body', 12110 'A comma-separated list of tag names that should <em>always</em> get inner indentation.'); 12111 12112 /** 12113 * Get indentation for given node 12114 * @param {AbbreviationNode} node 12115 * @returns {String} 12116 */ 12117 function getIndentation(node) { 12118 if (_.include(prefs.getArray('format.noIndentTags') || [], node.name())) { 12119 return ''; 12120 } 12121 12122 return require('resources').getVariable('indentation'); 12123 } 12124 12125 /** 12126 * Test if passed node has block-level sibling element 12127 * @param {AbbreviationNode} item 12128 * @return {Boolean} 12129 */ 12130 function hasBlockSibling(item) { 12131 return item.parent && require('abbreviationUtils').hasBlockChildren(item.parent); 12132 } 12133 12134 /** 12135 * Test if passed item is very first child in parsed tree 12136 * @param {AbbreviationNode} item 12137 */ 12138 function isVeryFirstChild(item) { 12139 return item.parent && !item.parent.parent && !item.index(); 12140 } 12141 12142 /** 12143 * Check if a newline should be added before element 12144 * @param {AbbreviationNode} node 12145 * @param {OutputProfile} profile 12146 * @return {Boolean} 12147 */ 12148 function shouldAddLineBreak(node, profile) { 12149 var abbrUtils = require('abbreviationUtils'); 12150 if (profile.tag_nl === true || abbrUtils.isBlock(node)) 12151 return true; 12152 12153 if (!node.parent || !profile.inline_break) 12154 return false; 12155 12156 // check if there are required amount of adjacent inline element 12157 return shouldFormatInline(node.parent, profile); 12158 } 12159 12160 /** 12161 * Need to add newline because <code>item</code> has too many inline children 12162 * @param {AbbreviationNode} node 12163 * @param {OutputProfile} profile 12164 */ 12165 function shouldBreakChild(node, profile) { 12166 // we need to test only one child element, because 12167 // hasBlockChildren() method will do the rest 12168 return node.children.length && shouldAddLineBreak(node.children[0], profile); 12169 } 12170 12171 function shouldFormatInline(node, profile) { 12172 var nodeCount = 0; 12173 var abbrUtils = require('abbreviationUtils'); 12174 return !!_.find(node.children, function(child) { 12175 if (child.isTextNode() || !abbrUtils.isInline(child)) 12176 nodeCount = 0; 12177 else if (abbrUtils.isInline(child)) 12178 nodeCount++; 12179 12180 if (nodeCount >= profile.inline_break) 12181 return true; 12182 }); 12183 } 12184 12185 function isRoot(item) { 12186 return !item.parent; 12187 } 12188 12189 /** 12190 * Processes element with matched resource of type <code>snippet</code> 12191 * @param {AbbreviationNode} item 12192 * @param {OutputProfile} profile 12193 */ 12194 function processSnippet(item, profile) { 12195 item.start = item.end = ''; 12196 if (!isVeryFirstChild(item) && profile.tag_nl !== false && shouldAddLineBreak(item, profile)) { 12197 // check if we’re not inside inline element 12198 if (isRoot(item.parent) || !require('abbreviationUtils').isInline(item.parent)) { 12199 item.start = require('utils').getNewline() + item.start; 12200 } 12201 } 12202 12203 return item; 12204 } 12205 12206 /** 12207 * Check if we should add line breaks inside inline element 12208 * @param {AbbreviationNode} node 12209 * @param {OutputProfile} profile 12210 * @return {Boolean} 12211 */ 12212 function shouldBreakInsideInline(node, profile) { 12213 var abbrUtils = require('abbreviationUtils'); 12214 var hasBlockElems = _.any(node.children, function(child) { 12215 if (abbrUtils.isSnippet(child)) 12216 return false; 12217 12218 return !abbrUtils.isInline(child); 12219 }); 12220 12221 if (!hasBlockElems) { 12222 return shouldFormatInline(node, profile); 12223 } 12224 12225 return true; 12226 } 12227 12228 /** 12229 * Processes element with <code>tag</code> type 12230 * @param {AbbreviationNode} item 12231 * @param {OutputProfile} profile 12232 */ 12233 function processTag(item, profile) { 12234 item.start = item.end = placeholder; 12235 var utils = require('utils'); 12236 var abbrUtils = require('abbreviationUtils'); 12237 var isUnary = abbrUtils.isUnary(item); 12238 var nl = utils.getNewline(); 12239 var indent = getIndentation(item); 12240 12241 // formatting output 12242 if (profile.tag_nl !== false) { 12243 var forceNl = profile.tag_nl === true && (profile.tag_nl_leaf || item.children.length); 12244 if (!forceNl) { 12245 forceNl = _.include(prefs.getArray('format.forceIndentationForTags') || [], item.name()); 12246 } 12247 12248 // formatting block-level elements 12249 if (!item.isTextNode()) { 12250 if (shouldAddLineBreak(item, profile)) { 12251 // - do not indent the very first element 12252 // - do not indent first child of a snippet 12253 if (!isVeryFirstChild(item) && (!abbrUtils.isSnippet(item.parent) || item.index())) 12254 item.start = nl + item.start; 12255 12256 if (abbrUtils.hasBlockChildren(item) || shouldBreakChild(item, profile) || (forceNl && !isUnary)) 12257 item.end = nl + item.end; 12258 12259 if (abbrUtils.hasTagsInContent(item) || (forceNl && !item.children.length && !isUnary)) 12260 item.start += nl + indent; 12261 } else if (abbrUtils.isInline(item) && hasBlockSibling(item) && !isVeryFirstChild(item)) { 12262 item.start = nl + item.start; 12263 } else if (abbrUtils.isInline(item) && shouldBreakInsideInline(item, profile)) { 12264 item.end = nl + item.end; 12265 } 12266 12267 item.padding = indent; 12268 } 12269 } 12270 12271 return item; 12272 } 12273 12274 /** 12275 * Processes simplified tree, making it suitable for output as HTML structure 12276 * @param {AbbreviationNode} tree 12277 * @param {OutputProfile} profile 12278 * @param {Number} level Depth level 12279 */ 12280 require('filters').add('_format', function process(tree, profile, level) { 12281 level = level || 0; 12282 var abbrUtils = require('abbreviationUtils'); 12283 12284 _.each(tree.children, function(item) { 12285 if (abbrUtils.isSnippet(item)) 12286 processSnippet(item, profile, level); 12287 else 12288 processTag(item, profile, level); 12289 12290 process(item, profile, level + 1); 12291 }); 12292 12293 return tree; 12294 }); 12295 });/** 12296 * Filter for producing HAML code from abbreviation. 12297 * @author Sergey Chikuyonok (serge.che@gmail.com) 12298 * @link http://chikuyonok.ru 12299 * @constructor 12300 * @memberOf __hamlFilterDefine 12301 * @param {Function} require 12302 * @param {Underscore} _ 12303 */ 12304 emmet.exec(function(require, _) { 12305 function transformClassName(className) { 12306 return require('utils').trim(className).replace(/\s+/g, '.'); 12307 } 12308 12309 /** 12310 * Creates HAML attributes string from tag according to profile settings 12311 * @param {AbbreviationNode} tag 12312 * @param {Object} profile 12313 */ 12314 function makeAttributesString(tag, profile) { 12315 var attrs = ''; 12316 var otherAttrs = []; 12317 var attrQuote = profile.attributeQuote(); 12318 var cursor = profile.cursor(); 12319 12320 _.each(tag.attributeList(), function(a) { 12321 var attrName = profile.attributeName(a.name); 12322 switch (attrName.toLowerCase()) { 12323 // use short notation for ID and CLASS attributes 12324 case 'id': 12325 attrs += '#' + (a.value || cursor); 12326 break; 12327 case 'class': 12328 attrs += '.' + transformClassName(a.value || cursor); 12329 break; 12330 // process other attributes 12331 default: 12332 otherAttrs.push(':' +attrName + ' => ' + attrQuote + (a.value || cursor) + attrQuote); 12333 } 12334 }); 12335 12336 if (otherAttrs.length) 12337 attrs += '{' + otherAttrs.join(', ') + '}'; 12338 12339 return attrs; 12340 } 12341 12342 /** 12343 * Processes element with <code>tag</code> type 12344 * @param {AbbreviationNode} item 12345 * @param {OutputProfile} profile 12346 */ 12347 function processTag(item, profile) { 12348 if (!item.parent) 12349 // looks like it's root element 12350 return item; 12351 12352 var abbrUtils = require('abbreviationUtils'); 12353 var utils = require('utils'); 12354 12355 var attrs = makeAttributesString(item, profile); 12356 var cursor = profile.cursor(); 12357 var isUnary = abbrUtils.isUnary(item); 12358 var selfClosing = profile.self_closing_tag && isUnary ? '/' : ''; 12359 var start= ''; 12360 12361 // define tag name 12362 var tagName = '%' + profile.tagName(item.name()); 12363 if (tagName.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1) 12364 // omit div tag 12365 tagName = ''; 12366 12367 item.end = ''; 12368 start = tagName + attrs + selfClosing + ' '; 12369 12370 var placeholder = '%s'; 12371 // We can't just replace placeholder with new value because 12372 // JavaScript will treat double $ character as a single one, assuming 12373 // we're using RegExp literal. 12374 item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder); 12375 12376 if (!item.children.length && !isUnary) 12377 item.start += cursor; 12378 12379 return item; 12380 } 12381 12382 /** 12383 * Processes simplified tree, making it suitable for output as HTML structure 12384 * @param {AbbreviationNode} tree 12385 * @param {Object} profile 12386 * @param {Number} level Depth level 12387 */ 12388 require('filters').add('haml', function process(tree, profile, level) { 12389 level = level || 0; 12390 var abbrUtils = require('abbreviationUtils'); 12391 12392 if (!level) { 12393 tree = require('filters').apply(tree, '_format', profile); 12394 } 12395 12396 _.each(tree.children, function(item) { 12397 if (!abbrUtils.isSnippet(item)) 12398 processTag(item, profile, level); 12399 12400 process(item, profile, level + 1); 12401 }); 12402 12403 return tree; 12404 }); 12405 });/** 12406 * Filter that produces HTML tree 12407 * @author Sergey Chikuyonok (serge.che@gmail.com) 12408 * @link http://chikuyonok.ru 12409 * @constructor 12410 * @memberOf __htmlFilterDefine 12411 * @param {Function} require 12412 * @param {Underscore} _ 12413 */ 12414 emmet.exec(function(require, _) { 12415 /** 12416 * Creates HTML attributes string from tag according to profile settings 12417 * @param {AbbreviationNode} node 12418 * @param {OutputProfile} profile 12419 */ 12420 function makeAttributesString(node, profile) { 12421 var attrQuote = profile.attributeQuote(); 12422 var cursor = profile.cursor(); 12423 12424 return _.map(node.attributeList(), function(a) { 12425 var attrName = profile.attributeName(a.name); 12426 return ' ' + attrName + '=' + attrQuote + (a.value || cursor) + attrQuote; 12427 }).join(''); 12428 } 12429 12430 /** 12431 * Processes element with <code>tag</code> type 12432 * @param {AbbreviationNode} item 12433 * @param {OutputProfile} profile 12434 */ 12435 function processTag(item, profile) { 12436 if (!item.parent) // looks like it's root element 12437 return item; 12438 12439 var abbrUtils = require('abbreviationUtils'); 12440 var utils = require('utils'); 12441 12442 var attrs = makeAttributesString(item, profile); 12443 var cursor = profile.cursor(); 12444 var isUnary = abbrUtils.isUnary(item); 12445 var start= ''; 12446 var end = ''; 12447 12448 // define opening and closing tags 12449 if (!item.isTextNode()) { 12450 var tagName = profile.tagName(item.name()); 12451 if (isUnary) { 12452 start = '<' + tagName + attrs + profile.selfClosing() + '>'; 12453 item.end = ''; 12454 } else { 12455 start = '<' + tagName + attrs + '>'; 12456 end = '</' + tagName + '>'; 12457 } 12458 } 12459 12460 var placeholder = '%s'; 12461 // We can't just replace placeholder with new value because 12462 // JavaScript will treat double $ character as a single one, assuming 12463 // we're using RegExp literal. 12464 item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder); 12465 item.end = utils.replaceSubstring(item.end, end, item.end.indexOf(placeholder), placeholder); 12466 12467 // should we put caret placeholder after opening tag? 12468 if ( 12469 !item.children.length 12470 && !isUnary 12471 && !~item.content.indexOf(cursor) 12472 && !require('tabStops').extract(item.content).tabstops.length 12473 ) { 12474 item.start += cursor; 12475 } 12476 12477 return item; 12478 } 12479 12480 /** 12481 * Processes simplified tree, making it suitable for output as HTML structure 12482 * @param {AbbreviationNode} tree 12483 * @param {Object} profile 12484 * @param {Number} level Depth level 12485 */ 12486 require('filters').add('html', function process(tree, profile, level) { 12487 level = level || 0; 12488 var abbrUtils = require('abbreviationUtils'); 12489 12490 if (!level) { 12491 tree = require('filters').apply(tree, '_format', profile); 12492 } 12493 12494 _.each(tree.children, function(item) { 12495 if (!abbrUtils.isSnippet(item)) 12496 processTag(item, profile, level); 12497 12498 process(item, profile, level + 1); 12499 }); 12500 12501 return tree; 12502 }); 12503 });/** 12504 * Output abbreviation on a single line (i.e. no line breaks) 12505 * @author Sergey Chikuyonok (serge.che@gmail.com) 12506 * @link http://chikuyonok.ru 12507 * @constructor 12508 * @memberOf __singleLineFilterDefine 12509 * @param {Function} require 12510 * @param {Underscore} _ 12511 */ 12512 emmet.exec(function(require, _) { 12513 var rePad = /^\s+/; 12514 var reNl = /[\n\r]/g; 12515 12516 require('filters').add('s', function process(tree) { 12517 var abbrUtils = require('abbreviationUtils'); 12518 12519 _.each(tree.children, function(item) { 12520 if (!abbrUtils.isSnippet(item)) { 12521 // remove padding from item 12522 item.start = item.start.replace(rePad, ''); 12523 item.end = item.end.replace(rePad, ''); 12524 } 12525 12526 // remove newlines 12527 item.start = item.start.replace(reNl, ''); 12528 item.end = item.end.replace(reNl, ''); 12529 item.content = item.content.replace(reNl, ''); 12530 12531 process(item); 12532 }); 12533 12534 return tree; 12535 }); 12536 }); 12537 /** 12538 * Trim filter: removes characters at the beginning of the text 12539 * content that indicates lists: numbers, #, *, -, etc. 12540 * 12541 * Useful for wrapping lists with abbreviation. 12542 * 12543 * @author Sergey Chikuyonok (serge.che@gmail.com) 12544 * @link http://chikuyonok.ru 12545 * 12546 * @constructor 12547 * @memberOf __trimFilterDefine 12548 * @param {Function} require 12549 * @param {Underscore} _ 12550 */ 12551 emmet.exec(function(require, _) { 12552 require('preferences').define('filter.trimRegexp', '[\\s|\\u00a0]*[\\d|#|\\-|\*|\\u2022]+\\.?\\s*', 12553 'Regular expression used to remove list markers (numbers, dashes, ' 12554 + 'bullets, etc.) in <code>t</code> (trim) filter. The trim filter ' 12555 + 'is useful for wrapping with abbreviation lists, pased from other ' 12556 + 'documents (for example, Word documents).'); 12557 12558 function process(tree, re) { 12559 _.each(tree.children, function(item) { 12560 if (item.content) 12561 item.content = item.content.replace(re, ''); 12562 12563 process(item, re); 12564 }); 12565 12566 return tree; 12567 } 12568 12569 require('filters').add('t', function(tree) { 12570 var re = new RegExp(require('preferences').get('filter.trimRegexp')); 12571 return process(tree, re); 12572 }); 12573 }); 12574 /** 12575 * Filter for trimming "select" attributes from some tags that contains 12576 * child elements 12577 * @author Sergey Chikuyonok (serge.che@gmail.com) 12578 * @link http://chikuyonok.ru 12579 * 12580 * @constructor 12581 * @memberOf __xslFilterDefine 12582 * @param {Function} require 12583 * @param {Underscore} _ 12584 */ 12585 emmet.exec(function(require, _) { 12586 var tags = { 12587 'xsl:variable': 1, 12588 'xsl:with-param': 1 12589 }; 12590 12591 /** 12592 * Removes "select" attribute from node 12593 * @param {AbbreviationNode} node 12594 */ 12595 function trimAttribute(node) { 12596 node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, ''); 12597 } 12598 12599 require('filters').add('xsl', function process(tree) { 12600 var abbrUtils = require('abbreviationUtils'); 12601 _.each(tree.children, function(item) { 12602 if (!abbrUtils.isSnippet(item) 12603 && (item.name() || '').toLowerCase() in tags 12604 && item.children.length) 12605 trimAttribute(item); 12606 process(item); 12607 }); 12608 12609 return tree; 12610 }); 12611 });/** 12612 * "Lorem ipsum" text generator. Matches <code>lipsum(num)?</code> or 12613 * <code>lorem(num)?</code> abbreviation. 12614 * This code is based on Django's contribution: 12615 * https://code.djangoproject.com/browser/django/trunk/django/contrib/webdesign/lorem_ipsum.py 12616 * <br><br> 12617 * Examples to test:<br> 12618 * <code>lipsum</code> – generates 30 words text.<br> 12619 * <code>lipsum*6</code> – generates 6 paragraphs (autowrapped with <p> element) of text.<br> 12620 * <code>ol>lipsum10*5</code> — generates ordered list with 5 list items (autowrapped with <li> tag) 12621 * with text of 10 words on each line<br> 12622 * <code>span*3>lipsum20</code> – generates 3 paragraphs of 20-words text, each wrapped with <span> element . 12623 * Each paragraph phrase is unique 12624 * @param {Function} require 12625 * @param {Underscore} _ 12626 * @constructor 12627 * @memberOf __loremIpsumGeneratorDefine 12628 */ 12629 emmet.define('lorem', function(require, _) { 12630 var langs = { 12631 en: { 12632 common: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipisicing', 'elit'], 12633 words: ['exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet', 12634 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi', 12635 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi', 12636 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos', 12637 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum', 12638 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus', 12639 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus', 12640 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum', 12641 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem', 12642 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus', 12643 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente', 12644 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet', 12645 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta', 12646 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima', 12647 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim', 12648 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores', 12649 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias', 12650 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea', 12651 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt', 12652 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate', 12653 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius', 12654 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos', 12655 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore', 12656 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo', 12657 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi', 12658 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam', 12659 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique', 12660 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere', 12661 'maxime', 'corrupti'] 12662 }, 12663 ru: { 12664 common: ['далеко-далеко', 'за', 'словесными', 'горами', 'в стране', 'гласных', 'и согласных', 'живут', 'рыбные', 'тексты'], 12665 words: ['вдали', 'от всех', 'они', 'буквенных', 'домах', 'на берегу', 'семантика', 12666 'большого', 'языкового', 'океана', 'маленький', 'ручеек', 'даль', 12667 'журчит', 'по всей', 'обеспечивает', 'ее','всеми', 'необходимыми', 12668 'правилами', 'эта', 'парадигматическая', 'страна', 'которой', 'жаренные', 12669 'предложения', 'залетают', 'прямо', 'рот', 'даже', 'всемогущая', 12670 'пунктуация', 'не', 'имеет', 'власти', 'над', 'рыбными', 'текстами', 12671 'ведущими', 'безорфографичный', 'образ', 'жизни', 'однажды', 'одна', 12672 'маленькая', 'строчка','рыбного', 'текста', 'имени', 'lorem', 'ipsum', 12673 'решила', 'выйти', 'большой', 'мир', 'грамматики', 'великий', 'оксмокс', 12674 'предупреждал', 'о', 'злых', 'запятых', 'диких', 'знаках', 'вопроса', 12675 'коварных', 'точках', 'запятой', 'но', 'текст', 'дал', 'сбить', 12676 'себя', 'толку', 'он', 'собрал', 'семь', 'своих', 'заглавных', 'букв', 12677 'подпоясал', 'инициал', 'за', 'пояс', 'пустился', 'дорогу', 12678 'взобравшись', 'первую', 'вершину', 'курсивных', 'гор', 'бросил', 12679 'последний', 'взгляд', 'назад', 'силуэт', 'своего', 'родного', 'города', 12680 'буквоград', 'заголовок', 'деревни', 'алфавит', 'подзаголовок', 'своего', 12681 'переулка', 'грустный', 'реторический', 'вопрос', 'скатился', 'его', 12682 'щеке', 'продолжил', 'свой', 'путь', 'дороге', 'встретил', 'рукопись', 12683 'она', 'предупредила', 'моей', 'все', 'переписывается', 'несколько', 12684 'раз', 'единственное', 'что', 'меня', 'осталось', 'это', 'приставка', 12685 'возвращайся', 'ты', 'лучше', 'свою', 'безопасную', 'страну', 'послушавшись', 12686 'рукописи', 'наш', 'продолжил', 'свой', 'путь', 'вскоре', 'ему', 12687 'повстречался', 'коварный', 'составитель', 'рекламных', 'текстов', 12688 'напоивший', 'языком', 'речью', 'заманивший', 'свое', 'агенство', 12689 'которое', 'использовало', 'снова', 'снова', 'своих', 'проектах', 12690 'если', 'переписали', 'то', 'живет', 'там', 'до', 'сих', 'пор'] 12691 } 12692 }; 12693 12694 var prefs = require('preferences'); 12695 prefs.define('lorem.defaultLang', 'en'); 12696 12697 /** 12698 * @param {AbbreviationNode} tree 12699 */ 12700 require('abbreviationParser').addPreprocessor(function(tree) { 12701 var re = /^(?:lorem|lipsum)([a-z]{2})?(\d*)$/i, match; 12702 12703 /** @param {AbbreviationNode} node */ 12704 tree.findAll(function(node) { 12705 if (node._name && (match = node._name.match(re))) { 12706 var wordCound = match[2] || 30; 12707 var lang = match[1] || prefs.get('lorem.defaultLang') || 'en'; 12708 12709 // force node name resolving if node should be repeated 12710 // or contains attributes. In this case, node should be outputed 12711 // as tag, otherwise as text-only node 12712 node._name = ''; 12713 node.data('forceNameResolving', node.isRepeating() || node.attributeList().length); 12714 node.data('pasteOverwrites', true); 12715 node.data('paste', function(i) { 12716 return paragraph(lang, wordCound, !i); 12717 }); 12718 } 12719 }); 12720 }); 12721 12722 /** 12723 * Returns random integer between <code>from</code> and <code>to</code> values 12724 * @param {Number} from 12725 * @param {Number} to 12726 * @returns {Number} 12727 */ 12728 function randint(from, to) { 12729 return Math.round(Math.random() * (to - from) + from); 12730 } 12731 12732 /** 12733 * @param {Array} arr 12734 * @param {Number} count 12735 * @returns {Array} 12736 */ 12737 function sample(arr, count) { 12738 var len = arr.length; 12739 var iterations = Math.min(len, count); 12740 var result = []; 12741 while (result.length < iterations) { 12742 var randIx = randint(0, len - 1); 12743 if (!_.include(result, randIx)) 12744 result.push(randIx); 12745 } 12746 12747 return _.map(result, function(ix) { 12748 return arr[ix]; 12749 }); 12750 } 12751 12752 function choice(val) { 12753 if (_.isString(val)) 12754 return val.charAt(randint(0, val.length - 1)); 12755 12756 return val[randint(0, val.length - 1)]; 12757 } 12758 12759 function sentence(words, end) { 12760 if (words.length) { 12761 words[0] = words[0].charAt(0).toUpperCase() + words[0].substring(1); 12762 } 12763 12764 return words.join(' ') + (end || choice('?!...')); // more dots that question marks 12765 } 12766 12767 /** 12768 * Insert commas at randomly selected words. This function modifies values 12769 * inside <code>words</code> array 12770 * @param {Array} words 12771 */ 12772 function insertCommas(words) { 12773 var len = words.length; 12774 var totalCommas = 0; 12775 12776 if (len > 3 && len <= 6) { 12777 totalCommas = randint(0, 1); 12778 } else if (len > 6 && len <= 12) { 12779 totalCommas = randint(0, 2); 12780 } else { 12781 totalCommas = randint(1, 4); 12782 } 12783 12784 _.each(_.range(totalCommas), function(ix) { 12785 if (ix < words.length - 1) { 12786 words[ix] += ','; 12787 } 12788 }); 12789 } 12790 12791 /** 12792 * Generate a paragraph of "Lorem ipsum" text 12793 * @param {Number} wordCount Words count in paragraph 12794 * @param {Boolean} startWithCommon Should paragraph start with common 12795 * "lorem ipsum" sentence. 12796 * @returns {String} 12797 */ 12798 function paragraph(lang, wordCount, startWithCommon) { 12799 var data = langs[lang]; 12800 if (!data) { 12801 return ''; 12802 } 12803 12804 var result = []; 12805 var totalWords = 0; 12806 var words; 12807 12808 wordCount = parseInt(wordCount, 10); 12809 12810 if (startWithCommon && data.common) { 12811 words = data.common.slice(0, wordCount); 12812 if (words.length > 5) 12813 words[4] += ','; 12814 totalWords += words.length; 12815 result.push(sentence(words, '.')); 12816 } 12817 12818 while (totalWords < wordCount) { 12819 words = sample(data.words, Math.min(randint(3, 12) * randint(1, 5), wordCount - totalWords)); 12820 totalWords += words.length; 12821 insertCommas(words); 12822 result.push(sentence(words)); 12823 } 12824 12825 return result.join(' '); 12826 } 12827 12828 return { 12829 /** 12830 * Adds new language words for Lorem Ipsum generator 12831 * @param {String} lang Two-letter lang definition 12832 * @param {Object} data Words for language. Maight be either a space-separated 12833 * list of words (String), Array of words or object with <code>text</code> and 12834 * <code>common</code> properties 12835 */ 12836 addLang: function(lang, data) { 12837 if (_.isString(data)) { 12838 data = {words: _.compact(data.split(' '))}; 12839 } else if (_.isArray(data)) { 12840 data = {words: data}; 12841 } 12842 12843 langs[lang] = data; 12844 } 12845 }; 12846 });/** 12847 * Select current line (for simple editors like browser's <textarea>) 12848 */ 12849 emmet.exec(function(require, _) { 12850 require('actions').add('select_line', function(editor) { 12851 var range = editor.getCurrentLineRange(); 12852 editor.createSelection(range.start, range.end); 12853 return true; 12854 }); 12855 });emmet.exec(function(require, _){require('resources').setVocabulary({ 12856 "variables": { 12857 "lang": "en", 12858 "locale": "en-US", 12859 "charset": "UTF-8", 12860 "indentation": "\t", 12861 "newline": "\n" 12862 }, 12863 12864 "css": { 12865 "filters": "html", 12866 "snippets": { 12867 "@i": "@import url(|);", 12868 "@import": "@import url(|);", 12869 "@m": "@media ${1:screen} {\n\t|\n}", 12870 "@media": "@media ${1:screen} {\n\t|\n}", 12871 "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}", 12872 "@f+": "@font-face {\n\tfont-family: '${1:FontName}';\n\tsrc: url('${2:FileName}.eot');\n\tsrc: url('${2:FileName}.eot?#iefix') format('embedded-opentype'),\n\t\t url('${2:FileName}.woff') format('woff'),\n\t\t url('${2:FileName}.ttf') format('truetype'),\n\t\t url('${2:FileName}.svg#${1:FontName}') format('svg');\n\tfont-style: ${3:normal};\n\tfont-weight: ${4:normal};\n}", 12873 12874 "@kf": "@-webkit-keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}\n@-o-keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}\n@-moz-keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}\n@keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}", 12875 12876 12877 "anim": "animation:|;", 12878 "anim-": "animation:${1:name} ${2:duration} ${3:timing-function} ${4:delay} ${5:iteration-count} ${6:direction} ${7:fill-mode};", 12879 "animdel": "animation-delay:${1:time};", 12880 12881 "animdir": "animation-direction:${1:normal};", 12882 "animdir:n": "animation-direction:normal;", 12883 "animdir:r": "animation-direction:reverse;", 12884 "animdir:a": "animation-direction:alternate;", 12885 "animdir:ar": "animation-direction:alternate-reverse;", 12886 12887 "animdur": "animation-duration:${1:0}s;", 12888 12889 "animfm": "animation-fill-mode:${1:both};", 12890 "animfm:f": "animation-fill-mode:forwards;", 12891 "animfm:b": "animation-fill-mode:backwards;", 12892 "animfm:bt": "animation-fill-mode:both;", 12893 "animfm:bh": "animation-fill-mode:both;", 12894 12895 "animic": "animation-iteration-count:${1:1};", 12896 "animic:i": "animation-iteration-count:infinite;", 12897 12898 "animn": "animation-name:${1:none};", 12899 12900 "animps": "animation-play-state:${1:running};", 12901 "animps:p": "animation-play-state:paused;", 12902 "animps:r": "animation-play-state:running;", 12903 12904 "animtf": "animation-timing-function:${1:linear};", 12905 "animtf:e": "animation-timing-function:ease;", 12906 "animtf:ei": "animation-timing-function:ease-in;", 12907 "animtf:eo": "animation-timing-function:ease-out;", 12908 "animtf:eio": "animation-timing-function:ease-in-out;", 12909 "animtf:l": "animation-timing-function:linear;", 12910 "animtf:cb": "animation-timing-function:cubic-bezier(${1:0.1}, ${2:0.7}, ${3:1.0}, ${3:0.1});", 12911 12912 "ap": "appearance:${none};", 12913 12914 "!": "!important", 12915 "pos": "position:${1:relative};", 12916 "pos:s": "position:static;", 12917 "pos:a": "position:absolute;", 12918 "pos:r": "position:relative;", 12919 "pos:f": "position:fixed;", 12920 "t": "top:|;", 12921 "t:a": "top:auto;", 12922 "r": "right:|;", 12923 "r:a": "right:auto;", 12924 "b": "bottom:|;", 12925 "b:a": "bottom:auto;", 12926 "l": "left:|;", 12927 "l:a": "left:auto;", 12928 "z": "z-index:|;", 12929 "z:a": "z-index:auto;", 12930 "fl": "float:${1:left};", 12931 "fl:n": "float:none;", 12932 "fl:l": "float:left;", 12933 "fl:r": "float:right;", 12934 "cl": "clear:${1:both};", 12935 "cl:n": "clear:none;", 12936 "cl:l": "clear:left;", 12937 "cl:r": "clear:right;", 12938 "cl:b": "clear:both;", 12939 12940 "colm": "columns:|;", 12941 "colmc": "column-count:|;", 12942 "colmf": "column-fill:|;", 12943 "colmg": "column-gap:|;", 12944 "colmr": "column-rule:|;", 12945 "colmrc": "column-rule-color:|;", 12946 "colmrs": "column-rule-style:|;", 12947 "colmrw": "column-rule-width:|;", 12948 "colms": "column-span:|;", 12949 "colmw": "column-width:|;", 12950 12951 "d": "display:${1:block};", 12952 "d:n": "display:none;", 12953 "d:b": "display:block;", 12954 "d:i": "display:inline;", 12955 "d:ib": "display:inline-block;", 12956 "d:li": "display:list-item;", 12957 "d:ri": "display:run-in;", 12958 "d:cp": "display:compact;", 12959 "d:tb": "display:table;", 12960 "d:itb": "display:inline-table;", 12961 "d:tbcp": "display:table-caption;", 12962 "d:tbcl": "display:table-column;", 12963 "d:tbclg": "display:table-column-group;", 12964 "d:tbhg": "display:table-header-group;", 12965 "d:tbfg": "display:table-footer-group;", 12966 "d:tbr": "display:table-row;", 12967 "d:tbrg": "display:table-row-group;", 12968 "d:tbc": "display:table-cell;", 12969 "d:rb": "display:ruby;", 12970 "d:rbb": "display:ruby-base;", 12971 "d:rbbg": "display:ruby-base-group;", 12972 "d:rbt": "display:ruby-text;", 12973 "d:rbtg": "display:ruby-text-group;", 12974 "v": "visibility:${1:hidden};", 12975 "v:v": "visibility:visible;", 12976 "v:h": "visibility:hidden;", 12977 "v:c": "visibility:collapse;", 12978 "ov": "overflow:${1:hidden};", 12979 "ov:v": "overflow:visible;", 12980 "ov:h": "overflow:hidden;", 12981 "ov:s": "overflow:scroll;", 12982 "ov:a": "overflow:auto;", 12983 "ovx": "overflow-x:${1:hidden};", 12984 "ovx:v": "overflow-x:visible;", 12985 "ovx:h": "overflow-x:hidden;", 12986 "ovx:s": "overflow-x:scroll;", 12987 "ovx:a": "overflow-x:auto;", 12988 "ovy": "overflow-y:${1:hidden};", 12989 "ovy:v": "overflow-y:visible;", 12990 "ovy:h": "overflow-y:hidden;", 12991 "ovy:s": "overflow-y:scroll;", 12992 "ovy:a": "overflow-y:auto;", 12993 "ovs": "overflow-style:${1:scrollbar};", 12994 "ovs:a": "overflow-style:auto;", 12995 "ovs:s": "overflow-style:scrollbar;", 12996 "ovs:p": "overflow-style:panner;", 12997 "ovs:m": "overflow-style:move;", 12998 "ovs:mq": "overflow-style:marquee;", 12999 "zoo": "zoom:1;", 13000 "zm": "zoom:1;", 13001 "cp": "clip:|;", 13002 "cp:a": "clip:auto;", 13003 "cp:r": "clip:rect(${1:top} ${2:right} ${3:bottom} ${4:left});", 13004 "bxz": "box-sizing:${1:border-box};", 13005 "bxz:cb": "box-sizing:content-box;", 13006 "bxz:bb": "box-sizing:border-box;", 13007 "bxsh": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:color};", 13008 "bxsh:r": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:spread }rgb(${6:0}, ${7:0}, ${8:0});", 13009 "bxsh:ra": "box-shadow:${1:inset }${2:h} ${3:v} ${4:blur} ${5:spread }rgba(${6:0}, ${7:0}, ${8:0}, .${9:5});", 13010 "bxsh:n": "box-shadow:none;", 13011 "m": "margin:|;", 13012 "m:a": "margin:auto;", 13013 "mt": "margin-top:|;", 13014 "mt:a": "margin-top:auto;", 13015 "mr": "margin-right:|;", 13016 "mr:a": "margin-right:auto;", 13017 "mb": "margin-bottom:|;", 13018 "mb:a": "margin-bottom:auto;", 13019 "ml": "margin-left:|;", 13020 "ml:a": "margin-left:auto;", 13021 "p": "padding:|;", 13022 "pt": "padding-top:|;", 13023 "pr": "padding-right:|;", 13024 "pb": "padding-bottom:|;", 13025 "pl": "padding-left:|;", 13026 "w": "width:|;", 13027 "w:a": "width:auto;", 13028 "h": "height:|;", 13029 "h:a": "height:auto;", 13030 "maw": "max-width:|;", 13031 "maw:n": "max-width:none;", 13032 "mah": "max-height:|;", 13033 "mah:n": "max-height:none;", 13034 "miw": "min-width:|;", 13035 "mih": "min-height:|;", 13036 "mar": "max-resolution:${1:res};", 13037 "mir": "min-resolution:${1:res};", 13038 "ori": "orientation:|;", 13039 "ori:l": "orientation:landscape;", 13040 "ori:p": "orientation:portrait;", 13041 "ol": "outline:|;", 13042 "ol:n": "outline:none;", 13043 "olo": "outline-offset:|;", 13044 "olw": "outline-width:|;", 13045 "olw:tn": "outline-width:thin;", 13046 "olw:m": "outline-width:medium;", 13047 "olw:tc": "outline-width:thick;", 13048 "ols": "outline-style:|;", 13049 "ols:n": "outline-style:none;", 13050 "ols:dt": "outline-style:dotted;", 13051 "ols:ds": "outline-style:dashed;", 13052 "ols:s": "outline-style:solid;", 13053 "ols:db": "outline-style:double;", 13054 "ols:g": "outline-style:groove;", 13055 "ols:r": "outline-style:ridge;", 13056 "ols:i": "outline-style:inset;", 13057 "ols:o": "outline-style:outset;", 13058 "olc": "outline-color:#${1:000};", 13059 "olc:i": "outline-color:invert;", 13060 "bd": "border:|;", 13061 "bd+": "border:${1:1px} ${2:solid} ${3:#000};", 13062 "bd:n": "border:none;", 13063 "bdbk": "border-break:${1:close};", 13064 "bdbk:c": "border-break:close;", 13065 "bdcl": "border-collapse:|;", 13066 "bdcl:c": "border-collapse:collapse;", 13067 "bdcl:s": "border-collapse:separate;", 13068 "bdc": "border-color:#${1:000};", 13069 "bdc:t": "border-color:transparent;", 13070 "bdi": "border-image:url(|);", 13071 "bdi:n": "border-image:none;", 13072 "bdti": "border-top-image:url(|);", 13073 "bdti:n": "border-top-image:none;", 13074 "bdri": "border-right-image:url(|);", 13075 "bdri:n": "border-right-image:none;", 13076 "bdbi": "border-bottom-image:url(|);", 13077 "bdbi:n": "border-bottom-image:none;", 13078 "bdli": "border-left-image:url(|);", 13079 "bdli:n": "border-left-image:none;", 13080 "bdci": "border-corner-image:url(|);", 13081 "bdci:n": "border-corner-image:none;", 13082 "bdci:c": "border-corner-image:continue;", 13083 "bdtli": "border-top-left-image:url(|);", 13084 "bdtli:n": "border-top-left-image:none;", 13085 "bdtli:c": "border-top-left-image:continue;", 13086 "bdtri": "border-top-right-image:url(|);", 13087 "bdtri:n": "border-top-right-image:none;", 13088 "bdtri:c": "border-top-right-image:continue;", 13089 "bdbri": "border-bottom-right-image:url(|);", 13090 "bdbri:n": "border-bottom-right-image:none;", 13091 "bdbri:c": "border-bottom-right-image:continue;", 13092 "bdbli": "border-bottom-left-image:url(|);", 13093 "bdbli:n": "border-bottom-left-image:none;", 13094 "bdbli:c": "border-bottom-left-image:continue;", 13095 "bdf": "border-fit:${1:repeat};", 13096 "bdf:c": "border-fit:clip;", 13097 "bdf:r": "border-fit:repeat;", 13098 "bdf:sc": "border-fit:scale;", 13099 "bdf:st": "border-fit:stretch;", 13100 "bdf:ow": "border-fit:overwrite;", 13101 "bdf:of": "border-fit:overflow;", 13102 "bdf:sp": "border-fit:space;", 13103 "bdlen": "border-length:|;", 13104 "bdlen:a": "border-length:auto;", 13105 "bdsp": "border-spacing:|;", 13106 "bds": "border-style:|;", 13107 "bds:n": "border-style:none;", 13108 "bds:h": "border-style:hidden;", 13109 "bds:dt": "border-style:dotted;", 13110 "bds:ds": "border-style:dashed;", 13111 "bds:s": "border-style:solid;", 13112 "bds:db": "border-style:double;", 13113 "bds:dtds": "border-style:dot-dash;", 13114 "bds:dtdtds": "border-style:dot-dot-dash;", 13115 "bds:w": "border-style:wave;", 13116 "bds:g": "border-style:groove;", 13117 "bds:r": "border-style:ridge;", 13118 "bds:i": "border-style:inset;", 13119 "bds:o": "border-style:outset;", 13120 "bdw": "border-width:|;", 13121 "bdtw": "border-top-width:|;", 13122 "bdrw": "border-right-width:|;", 13123 "bdbw": "border-bottom-width:|;", 13124 "bdlw": "border-left-width:|;", 13125 "bdt": "border-top:|;", 13126 "bt": "border-top:|;", 13127 "bdt+": "border-top:${1:1px} ${2:solid} ${3:#000};", 13128 "bdt:n": "border-top:none;", 13129 "bdts": "border-top-style:|;", 13130 "bdts:n": "border-top-style:none;", 13131 "bdtc": "border-top-color:#${1:000};", 13132 "bdtc:t": "border-top-color:transparent;", 13133 "bdr": "border-right:|;", 13134 "br": "border-right:|;", 13135 "bdr+": "border-right:${1:1px} ${2:solid} ${3:#000};", 13136 "bdr:n": "border-right:none;", 13137 "bdrst": "border-right-style:|;", 13138 "bdrst:n": "border-right-style:none;", 13139 "bdrc": "border-right-color:#${1:000};", 13140 "bdrc:t": "border-right-color:transparent;", 13141 "bdb": "border-bottom:|;", 13142 "bb": "border-bottom:|;", 13143 "bdb+": "border-bottom:${1:1px} ${2:solid} ${3:#000};", 13144 "bdb:n": "border-bottom:none;", 13145 "bdbs": "border-bottom-style:|;", 13146 "bdbs:n": "border-bottom-style:none;", 13147 "bdbc": "border-bottom-color:#${1:000};", 13148 "bdbc:t": "border-bottom-color:transparent;", 13149 "bdl": "border-left:|;", 13150 "bl": "border-left:|;", 13151 "bdl+": "border-left:${1:1px} ${2:solid} ${3:#000};", 13152 "bdl:n": "border-left:none;", 13153 "bdls": "border-left-style:|;", 13154 "bdls:n": "border-left-style:none;", 13155 "bdlc": "border-left-color:#${1:000};", 13156 "bdlc:t": "border-left-color:transparent;", 13157 "bdrs": "border-radius:|;", 13158 "bdtrrs": "border-top-right-radius:|;", 13159 "bdtlrs": "border-top-left-radius:|;", 13160 "bdbrrs": "border-bottom-right-radius:|;", 13161 "bdblrs": "border-bottom-left-radius:|;", 13162 "bg": "background:#${1:000};", 13163 "bg+": "background:${1:#fff} url(${2}) ${3:0} ${4:0} ${5:no-repeat};", 13164 "bg:n": "background:none;", 13165 "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');", 13166 "bgc": "background-color:#${1:fff};", 13167 "bgc:t": "background-color:transparent;", 13168 "bgi": "background-image:url(|);", 13169 "bgi:n": "background-image:none;", 13170 "bgr": "background-repeat:|;", 13171 "bgr:n": "background-repeat:no-repeat;", 13172 "bgr:x": "background-repeat:repeat-x;", 13173 "bgr:y": "background-repeat:repeat-y;", 13174 "bgr:sp": "background-repeat:space;", 13175 "bgr:rd": "background-repeat:round;", 13176 "bga": "background-attachment:|;", 13177 "bga:f": "background-attachment:fixed;", 13178 "bga:s": "background-attachment:scroll;", 13179 "bgp": "background-position:${1:0} ${2:0};", 13180 "bgpx": "background-position-x:|;", 13181 "bgpy": "background-position-y:|;", 13182 "bgbk": "background-break:|;", 13183 "bgbk:bb": "background-break:bounding-box;", 13184 "bgbk:eb": "background-break:each-box;", 13185 "bgbk:c": "background-break:continuous;", 13186 "bgcp": "background-clip:${1:padding-box};", 13187 "bgcp:bb": "background-clip:border-box;", 13188 "bgcp:pb": "background-clip:padding-box;", 13189 "bgcp:cb": "background-clip:content-box;", 13190 "bgcp:nc": "background-clip:no-clip;", 13191 "bgo": "background-origin:|;", 13192 "bgo:pb": "background-origin:padding-box;", 13193 "bgo:bb": "background-origin:border-box;", 13194 "bgo:cb": "background-origin:content-box;", 13195 "bgsz": "background-size:|;", 13196 "bgsz:a": "background-size:auto;", 13197 "bgsz:ct": "background-size:contain;", 13198 "bgsz:cv": "background-size:cover;", 13199 "c": "color:#${1:000};", 13200 "c:r": "color:rgb(${1:0}, ${2:0}, ${3:0});", 13201 "c:ra": "color:rgba(${1:0}, ${2:0}, ${3:0}, .${4:5});", 13202 "cm": "/* |${child} */", 13203 "cnt": "content:'|';", 13204 "cnt:n": "content:normal;", 13205 "cnt:oq": "content:open-quote;", 13206 "cnt:noq": "content:no-open-quote;", 13207 "cnt:cq": "content:close-quote;", 13208 "cnt:ncq": "content:no-close-quote;", 13209 "cnt:a": "content:attr(|);", 13210 "cnt:c": "content:counter(|);", 13211 "cnt:cs": "content:counters(|);", 13212 13213 13214 "tbl": "table-layout:|;", 13215 "tbl:a": "table-layout:auto;", 13216 "tbl:f": "table-layout:fixed;", 13217 "cps": "caption-side:|;", 13218 "cps:t": "caption-side:top;", 13219 "cps:b": "caption-side:bottom;", 13220 "ec": "empty-cells:|;", 13221 "ec:s": "empty-cells:show;", 13222 "ec:h": "empty-cells:hide;", 13223 "lis": "list-style:|;", 13224 "lis:n": "list-style:none;", 13225 "lisp": "list-style-position:|;", 13226 "lisp:i": "list-style-position:inside;", 13227 "lisp:o": "list-style-position:outside;", 13228 "list": "list-style-type:|;", 13229 "list:n": "list-style-type:none;", 13230 "list:d": "list-style-type:disc;", 13231 "list:c": "list-style-type:circle;", 13232 "list:s": "list-style-type:square;", 13233 "list:dc": "list-style-type:decimal;", 13234 "list:dclz": "list-style-type:decimal-leading-zero;", 13235 "list:lr": "list-style-type:lower-roman;", 13236 "list:ur": "list-style-type:upper-roman;", 13237 "lisi": "list-style-image:|;", 13238 "lisi:n": "list-style-image:none;", 13239 "q": "quotes:|;", 13240 "q:n": "quotes:none;", 13241 "q:ru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C';", 13242 "q:en": "quotes:'\\201C' '\\201D' '\\2018' '\\2019';", 13243 "ct": "content:|;", 13244 "ct:n": "content:normal;", 13245 "ct:oq": "content:open-quote;", 13246 "ct:noq": "content:no-open-quote;", 13247 "ct:cq": "content:close-quote;", 13248 "ct:ncq": "content:no-close-quote;", 13249 "ct:a": "content:attr(|);", 13250 "ct:c": "content:counter(|);", 13251 "ct:cs": "content:counters(|);", 13252 "coi": "counter-increment:|;", 13253 "cor": "counter-reset:|;", 13254 "va": "vertical-align:${1:top};", 13255 "va:sup": "vertical-align:super;", 13256 "va:t": "vertical-align:top;", 13257 "va:tt": "vertical-align:text-top;", 13258 "va:m": "vertical-align:middle;", 13259 "va:bl": "vertical-align:baseline;", 13260 "va:b": "vertical-align:bottom;", 13261 "va:tb": "vertical-align:text-bottom;", 13262 "va:sub": "vertical-align:sub;", 13263 "ta": "text-align:${1:left};", 13264 "ta:l": "text-align:left;", 13265 "ta:c": "text-align:center;", 13266 "ta:r": "text-align:right;", 13267 "ta:j": "text-align:justify;", 13268 "ta-lst": "text-align-last:|;", 13269 "tal:a": "text-align-last:auto;", 13270 "tal:l": "text-align-last:left;", 13271 "tal:c": "text-align-last:center;", 13272 "tal:r": "text-align-last:right;", 13273 "td": "text-decoration:${1:none};", 13274 "td:n": "text-decoration:none;", 13275 "td:u": "text-decoration:underline;", 13276 "td:o": "text-decoration:overline;", 13277 "td:l": "text-decoration:line-through;", 13278 "te": "text-emphasis:|;", 13279 "te:n": "text-emphasis:none;", 13280 "te:ac": "text-emphasis:accent;", 13281 "te:dt": "text-emphasis:dot;", 13282 "te:c": "text-emphasis:circle;", 13283 "te:ds": "text-emphasis:disc;", 13284 "te:b": "text-emphasis:before;", 13285 "te:a": "text-emphasis:after;", 13286 "th": "text-height:|;", 13287 "th:a": "text-height:auto;", 13288 "th:f": "text-height:font-size;", 13289 "th:t": "text-height:text-size;", 13290 "th:m": "text-height:max-size;", 13291 "ti": "text-indent:|;", 13292 "ti:-": "text-indent:-9999px;", 13293 "tj": "text-justify:|;", 13294 "tj:a": "text-justify:auto;", 13295 "tj:iw": "text-justify:inter-word;", 13296 "tj:ii": "text-justify:inter-ideograph;", 13297 "tj:ic": "text-justify:inter-cluster;", 13298 "tj:d": "text-justify:distribute;", 13299 "tj:k": "text-justify:kashida;", 13300 "tj:t": "text-justify:tibetan;", 13301 "tov": "text-overflow:${ellipsis};", 13302 "tov:e": "text-overflow:ellipsis;", 13303 "tov:c": "text-overflow:clip;", 13304 "to": "text-outline:|;", 13305 "to+": "text-outline:${1:0} ${2:0} ${3:#000};", 13306 "to:n": "text-outline:none;", 13307 "tr": "text-replace:|;", 13308 "tr:n": "text-replace:none;", 13309 "tt": "text-transform:${1:uppercase};", 13310 "tt:n": "text-transform:none;", 13311 "tt:c": "text-transform:capitalize;", 13312 "tt:u": "text-transform:uppercase;", 13313 "tt:l": "text-transform:lowercase;", 13314 "tw": "text-wrap:|;", 13315 "tw:n": "text-wrap:normal;", 13316 "tw:no": "text-wrap:none;", 13317 "tw:u": "text-wrap:unrestricted;", 13318 "tw:s": "text-wrap:suppress;", 13319 "tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000};", 13320 "tsh:r": "text-shadow:${1:h} ${2:v} ${3:blur} rgb(${4:0}, ${5:0}, ${6:0});", 13321 "tsh:ra": "text-shadow:${1:h} ${2:v} ${3:blur} rgba(${4:0}, ${5:0}, ${6:0}, .${7:5});", 13322 "tsh+": "text-shadow:${1:0} ${2:0} ${3:0} ${4:#000};", 13323 "tsh:n": "text-shadow:none;", 13324 "trf": "transform:|;", 13325 "trf:skx": "transform: skewX(${1:angle});", 13326 "trf:sky": "transform: skewY(${1:angle});", 13327 "trf:sc": "transform: scale(${1:x}, ${2:y});", 13328 "trf:scx": "transform: scaleX(${1:x});", 13329 "trf:scy": "transform: scaleY(${1:y});", 13330 "trf:r": "transform: rotate(${1:angle});", 13331 "trf:t": "transform: translate(${1:x}, ${2:y});", 13332 "trf:tx": "transform: translateX(${1:x});", 13333 "trf:ty": "transform: translateY(${1:y});", 13334 "trfo": "transform-origin:|;", 13335 "trfs": "transform-style:${1:preserve-3d};", 13336 "trs": "transition:${1:prop} ${2:time};", 13337 "trsde": "transition-delay:${1:time};", 13338 "trsdu": "transition-duration:${1:time};", 13339 "trsp": "transition-property:${1:prop};", 13340 "trstf": "transition-timing-function:${1:tfunc};", 13341 "lh": "line-height:|;", 13342 "whs": "white-space:|;", 13343 "whs:n": "white-space:normal;", 13344 "whs:p": "white-space:pre;", 13345 "whs:nw": "white-space:nowrap;", 13346 "whs:pw": "white-space:pre-wrap;", 13347 "whs:pl": "white-space:pre-line;", 13348 "whsc": "white-space-collapse:|;", 13349 "whsc:n": "white-space-collapse:normal;", 13350 "whsc:k": "white-space-collapse:keep-all;", 13351 "whsc:l": "white-space-collapse:loose;", 13352 "whsc:bs": "white-space-collapse:break-strict;", 13353 "whsc:ba": "white-space-collapse:break-all;", 13354 "wob": "word-break:|;", 13355 "wob:n": "word-break:normal;", 13356 "wob:k": "word-break:keep-all;", 13357 "wob:ba": "word-break:break-all;", 13358 "wos": "word-spacing:|;", 13359 "wow": "word-wrap:|;", 13360 "wow:nm": "word-wrap:normal;", 13361 "wow:n": "word-wrap:none;", 13362 "wow:u": "word-wrap:unrestricted;", 13363 "wow:s": "word-wrap:suppress;", 13364 "wow:b": "word-wrap:break-word;", 13365 "wm": "writing-mode:${1:lr-tb};", 13366 "wm:lrt": "writing-mode:lr-tb;", 13367 "wm:lrb": "writing-mode:lr-bt;", 13368 "wm:rlt": "writing-mode:rl-tb;", 13369 "wm:rlb": "writing-mode:rl-bt;", 13370 "wm:tbr": "writing-mode:tb-rl;", 13371 "wm:tbl": "writing-mode:tb-lr;", 13372 "wm:btl": "writing-mode:bt-lr;", 13373 "wm:btr": "writing-mode:bt-rl;", 13374 "lts": "letter-spacing:|;", 13375 "lts-n": "letter-spacing:normal;", 13376 "f": "font:|;", 13377 "f+": "font:${1:1em} ${2:Arial,sans-serif};", 13378 "fw": "font-weight:|;", 13379 "fw:n": "font-weight:normal;", 13380 "fw:b": "font-weight:bold;", 13381 "fw:br": "font-weight:bolder;", 13382 "fw:lr": "font-weight:lighter;", 13383 "fs": "font-style:${italic};", 13384 "fs:n": "font-style:normal;", 13385 "fs:i": "font-style:italic;", 13386 "fs:o": "font-style:oblique;", 13387 "fv": "font-variant:|;", 13388 "fv:n": "font-variant:normal;", 13389 "fv:sc": "font-variant:small-caps;", 13390 "fz": "font-size:|;", 13391 "fza": "font-size-adjust:|;", 13392 "fza:n": "font-size-adjust:none;", 13393 "ff": "font-family:|;", 13394 "ff:s": "font-family:serif;", 13395 "ff:ss": "font-family:sans-serif;", 13396 "ff:c": "font-family:cursive;", 13397 "ff:f": "font-family:fantasy;", 13398 "ff:m": "font-family:monospace;", 13399 "ff:a": "font-family: Arial, \"Helvetica Neue\", Helvetica, sans-serif;", 13400 "ff:t": "font-family: \"Times New Roman\", Times, Baskerville, Georgia, serif;", 13401 "ff:v": "font-family: Verdana, Geneva, sans-serif;", 13402 "fef": "font-effect:|;", 13403 "fef:n": "font-effect:none;", 13404 "fef:eg": "font-effect:engrave;", 13405 "fef:eb": "font-effect:emboss;", 13406 "fef:o": "font-effect:outline;", 13407 "fem": "font-emphasize:|;", 13408 "femp": "font-emphasize-position:|;", 13409 "femp:b": "font-emphasize-position:before;", 13410 "femp:a": "font-emphasize-position:after;", 13411 "fems": "font-emphasize-style:|;", 13412 "fems:n": "font-emphasize-style:none;", 13413 "fems:ac": "font-emphasize-style:accent;", 13414 "fems:dt": "font-emphasize-style:dot;", 13415 "fems:c": "font-emphasize-style:circle;", 13416 "fems:ds": "font-emphasize-style:disc;", 13417 "fsm": "font-smooth:|;", 13418 "fsm:a": "font-smooth:auto;", 13419 "fsm:n": "font-smooth:never;", 13420 "fsm:aw": "font-smooth:always;", 13421 "fst": "font-stretch:|;", 13422 "fst:n": "font-stretch:normal;", 13423 "fst:uc": "font-stretch:ultra-condensed;", 13424 "fst:ec": "font-stretch:extra-condensed;", 13425 "fst:c": "font-stretch:condensed;", 13426 "fst:sc": "font-stretch:semi-condensed;", 13427 "fst:se": "font-stretch:semi-expanded;", 13428 "fst:e": "font-stretch:expanded;", 13429 "fst:ee": "font-stretch:extra-expanded;", 13430 "fst:ue": "font-stretch:ultra-expanded;", 13431 "op": "opacity:|;", 13432 "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);", 13433 "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';", 13434 "rsz": "resize:|;", 13435 "rsz:n": "resize:none;", 13436 "rsz:b": "resize:both;", 13437 "rsz:h": "resize:horizontal;", 13438 "rsz:v": "resize:vertical;", 13439 "cur": "cursor:${pointer};", 13440 "cur:a": "cursor:auto;", 13441 "cur:d": "cursor:default;", 13442 "cur:c": "cursor:crosshair;", 13443 "cur:ha": "cursor:hand;", 13444 "cur:he": "cursor:help;", 13445 "cur:m": "cursor:move;", 13446 "cur:p": "cursor:pointer;", 13447 "cur:t": "cursor:text;", 13448 "pgbb": "page-break-before:|;", 13449 "pgbb:au": "page-break-before:auto;", 13450 "pgbb:al": "page-break-before:always;", 13451 "pgbb:l": "page-break-before:left;", 13452 "pgbb:r": "page-break-before:right;", 13453 "pgbi": "page-break-inside:|;", 13454 "pgbi:au": "page-break-inside:auto;", 13455 "pgbi:av": "page-break-inside:avoid;", 13456 "pgba": "page-break-after:|;", 13457 "pgba:au": "page-break-after:auto;", 13458 "pgba:al": "page-break-after:always;", 13459 "pgba:l": "page-break-after:left;", 13460 "pgba:r": "page-break-after:right;", 13461 "orp": "orphans:|;", 13462 "us": "user-select:${none};", 13463 "wid": "widows:|;", 13464 "wfsm": "-webkit-font-smoothing:${antialiased};", 13465 "wfsm:a": "-webkit-font-smoothing:antialiased;", 13466 "wfsm:s": "-webkit-font-smoothing:subpixel-antialiased;", 13467 "wfsm:sa": "-webkit-font-smoothing:subpixel-antialiased;", 13468 "wfsm:n": "-webkit-font-smoothing:none;" 13469 } 13470 }, 13471 13472 "html": { 13473 "filters": "html", 13474 "profile": "html", 13475 "snippets": { 13476 "!!!": "<!doctype html>", 13477 "!!!4t": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">", 13478 "!!!4s": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">", 13479 "!!!xt": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">", 13480 "!!!xs": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">", 13481 "!!!xxs": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">", 13482 13483 "c": "<!-- |${child} -->", 13484 "cc:ie6": "<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->", 13485 "cc:ie": "<!--[if IE]>\n\t${child}|\n<![endif]-->", 13486 "cc:noie": "<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->" 13487 }, 13488 13489 "abbreviations": { 13490 "!": "html:5", 13491 "a": "<a href=\"\">", 13492 "a:link": "<a href=\"http://|\">", 13493 "a:mail": "<a href=\"mailto:|\">", 13494 "abbr": "<abbr title=\"\">", 13495 "acronym": "<acronym title=\"\">", 13496 "base": "<base href=\"\" />", 13497 "basefont": "<basefont/>", 13498 "br": "<br/>", 13499 "frame": "<frame/>", 13500 "hr": "<hr/>", 13501 "bdo": "<bdo dir=\"\">", 13502 "bdo:r": "<bdo dir=\"rtl\">", 13503 "bdo:l": "<bdo dir=\"ltr\">", 13504 "col": "<col/>", 13505 "link": "<link rel=\"stylesheet\" href=\"\" />", 13506 "link:css": "<link rel=\"stylesheet\" href=\"${1:style}.css\" />", 13507 "link:print": "<link rel=\"stylesheet\" href=\"${1:print}.css\" media=\"print\" />", 13508 "link:favicon": "<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"${1:favicon.ico}\" />", 13509 "link:touch": "<link rel=\"apple-touch-icon\" href=\"${1:favicon.png}\" />", 13510 "link:rss": "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"${1:rss.xml}\" />", 13511 "link:atom": "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"Atom\" href=\"${1:atom.xml}\" />", 13512 "meta": "<meta/>", 13513 "meta:utf": "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\" />", 13514 "meta:win": "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=windows-1251\" />", 13515 "meta:vp": "<meta name=\"viewport\" content=\"width=${1:device-width}, user-scalable=${2:no}, initial-scale=${3:1.0}, maximum-scale=${4:1.0}, minimum-scale=${5:1.0}\" />", 13516 "meta:compat": "<meta http-equiv=\"X-UA-Compatible\" content=\"${1:IE=7}\" />", 13517 "style": "<style>", 13518 "script": "<script>", 13519 "script:src": "<script src=\"\">", 13520 "img": "<img src=\"\" alt=\"\" />", 13521 "iframe": "<iframe src=\"\" frameborder=\"0\">", 13522 "embed": "<embed src=\"\" type=\"\" />", 13523 "object": "<object data=\"\" type=\"\">", 13524 "param": "<param name=\"\" value=\"\" />", 13525 "map": "<map name=\"\">", 13526 "area": "<area shape=\"\" coords=\"\" href=\"\" alt=\"\" />", 13527 "area:d": "<area shape=\"default\" href=\"\" alt=\"\" />", 13528 "area:c": "<area shape=\"circle\" coords=\"\" href=\"\" alt=\"\" />", 13529 "area:r": "<area shape=\"rect\" coords=\"\" href=\"\" alt=\"\" />", 13530 "area:p": "<area shape=\"poly\" coords=\"\" href=\"\" alt=\"\" />", 13531 "form": "<form action=\"\">", 13532 "form:get": "<form action=\"\" method=\"get\">", 13533 "form:post": "<form action=\"\" method=\"post\">", 13534 "label": "<label for=\"\">", 13535 "input": "<input type=\"${1:text}\" />", 13536 "inp": "<input type=\"${1:text}\" name=\"\" id=\"\" />", 13537 "input:hidden": "input[type=hidden name]", 13538 "input:h": "input:hidden", 13539 "input:text": "inp", 13540 "input:t": "inp", 13541 "input:search": "inp[type=search]", 13542 "input:email": "inp[type=email]", 13543 "input:url": "inp[type=url]", 13544 "input:password": "inp[type=password]", 13545 "input:p": "input:password", 13546 "input:datetime": "inp[type=datetime]", 13547 "input:date": "inp[type=date]", 13548 "input:datetime-local": "inp[type=datetime-local]", 13549 "input:month": "inp[type=month]", 13550 "input:week": "inp[type=week]", 13551 "input:time": "inp[type=time]", 13552 "input:number": "inp[type=number]", 13553 "input:color": "inp[type=color]", 13554 "input:checkbox": "inp[type=checkbox]", 13555 "input:c": "input:checkbox", 13556 "input:radio": "inp[type=radio]", 13557 "input:r": "input:radio", 13558 "input:range": "inp[type=range]", 13559 "input:file": "inp[type=file]", 13560 "input:f": "input:file", 13561 "input:submit": "<input type=\"submit\" value=\"\" />", 13562 "input:s": "input:submit", 13563 "input:image": "<input type=\"image\" src=\"\" alt=\"\" />", 13564 "input:i": "input:image", 13565 "input:button": "<input type=\"button\" value=\"\" />", 13566 "input:b": "input:button", 13567 "isindex": "<isindex/>", 13568 "input:reset": "input:button[type=reset]", 13569 "select": "<select name=\"\" id=\"\">", 13570 "select:disabled": "select[disabled]", 13571 "select:d": "select[disabled]", 13572 "option": "<option value=\"\">", 13573 "textarea": "<textarea name=\"\" id=\"\" cols=\"${1:30}\" rows=\"${2:10}\">", 13574 "marquee": "<marquee behavior=\"\" direction=\"\">", 13575 "menu:context": "menu[type=context]>", 13576 "menu:c": "menu:context", 13577 "menu:toolbar": "menu[type=toolbar]>", 13578 "menu:t": "menu:toolbar", 13579 "video": "<video src=\"\">", 13580 "audio": "<audio src=\"\">", 13581 "html:xml": "<html xmlns=\"http://www.w3.org/1999/xhtml\">", 13582 "keygen": "<keygen/>", 13583 "command": "<command/>", 13584 "button:submit" : "button[type=submit]", 13585 "button:s" : "button[type=submit]", 13586 "button:reset" : "button[type=reset]", 13587 "button:r" : "button[type=reset]", 13588 "button:disabled" : "button[disabled]", 13589 "button:d" : "button[disabled]", 13590 "fieldset:disabled" : "fieldset[disabled]", 13591 "fieldset:d" : "fieldset[disabled]", 13592 13593 "bq": "blockquote", 13594 "acr": "acronym", 13595 "fig": "figure", 13596 "figc": "figcaption", 13597 "ifr": "iframe", 13598 "emb": "embed", 13599 "obj": "object", 13600 "src": "source", 13601 "cap": "caption", 13602 "colg": "colgroup", 13603 "fst": "fieldset", 13604 "fst:d": "fieldset[disabled]", 13605 "btn": "button", 13606 "btn:b": "button[type=button]", 13607 "btn:r": "button[type=reset]", 13608 "btn:s": "button[type=submit]", 13609 "btn:d": "button[disabled]", 13610 "optg": "optgroup", 13611 "opt": "option", 13612 "tarea": "textarea", 13613 "leg": "legend", 13614 "sect": "section", 13615 "art": "article", 13616 "hdr": "header", 13617 "ftr": "footer", 13618 "adr": "address", 13619 "dlg": "dialog", 13620 "str": "strong", 13621 "prog": "progress", 13622 "fset": "fieldset", 13623 "fset:d": "fieldset[disabled]", 13624 "datag": "datagrid", 13625 "datal": "datalist", 13626 "kg": "keygen", 13627 "out": "output", 13628 "det": "details", 13629 "cmd": "command", 13630 "doc": "html>(head>meta[charset=UTF-8]+title{${1:Document}})+body", 13631 "doc4": "html>(head>meta[http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\"]+title{${1:Document}})+body", 13632 13633 "html:4t": "!!!4t+doc4[lang=${lang}]", 13634 "html:4s": "!!!4s+doc4[lang=${lang}]", 13635 "html:xt": "!!!xt+doc4[xmlns=http://www.w3.org/1999/xhtml xml:lang=${lang}]", 13636 "html:xs": "!!!xs+doc4[xmlns=http://www.w3.org/1999/xhtml xml:lang=${lang}]", 13637 "html:xxs": "!!!xxs+doc4[xmlns=http://www.w3.org/1999/xhtml xml:lang=${lang}]", 13638 "html:5": "!!!+doc[lang=${lang}]", 13639 13640 "ol+": "ol>li", 13641 "ul+": "ul>li", 13642 "dl+": "dl>dt+dd", 13643 "map+": "map>area", 13644 "table+": "table>tr>td", 13645 "colgroup+": "colgroup>col", 13646 "colg+": "colgroup>col", 13647 "tr+": "tr>td", 13648 "select+": "select>option", 13649 "optgroup+": "optgroup>option", 13650 "optg+": "optgroup>option" 13651 } 13652 }, 13653 13654 "xml": { 13655 "extends": "html", 13656 "profile": "xml", 13657 "filters": "html" 13658 }, 13659 13660 "xsl": { 13661 "extends": "html", 13662 "profile": "xml", 13663 "filters": "html, xsl", 13664 "abbreviations": { 13665 "tm": "<xsl:template match=\"\" mode=\"\">", 13666 "tmatch": "tm", 13667 "tn": "<xsl:template name=\"\">", 13668 "tname": "tn", 13669 "call": "<xsl:call-template name=\"\"/>", 13670 "ap": "<xsl:apply-templates select=\"\" mode=\"\"/>", 13671 "api": "<xsl:apply-imports/>", 13672 "imp": "<xsl:import href=\"\"/>", 13673 "inc": "<xsl:include href=\"\"/>", 13674 13675 "ch": "<xsl:choose>", 13676 "xsl:when": "<xsl:when test=\"\">", 13677 "wh": "xsl:when", 13678 "ot": "<xsl:otherwise>", 13679 "if": "<xsl:if test=\"\">", 13680 13681 "par": "<xsl:param name=\"\">", 13682 "pare": "<xsl:param name=\"\" select=\"\"/>", 13683 "var": "<xsl:variable name=\"\">", 13684 "vare": "<xsl:variable name=\"\" select=\"\"/>", 13685 "wp": "<xsl:with-param name=\"\" select=\"\"/>", 13686 "key": "<xsl:key name=\"\" match=\"\" use=\"\"/>", 13687 13688 "elem": "<xsl:element name=\"\">", 13689 "attr": "<xsl:attribute name=\"\">", 13690 "attrs": "<xsl:attribute-set name=\"\">", 13691 13692 "cp": "<xsl:copy select=\"\"/>", 13693 "co": "<xsl:copy-of select=\"\"/>", 13694 "val": "<xsl:value-of select=\"\"/>", 13695 "each": "<xsl:for-each select=\"\">", 13696 "for": "each", 13697 "tex": "<xsl:text></xsl:text>", 13698 13699 "com": "<xsl:comment>", 13700 "msg": "<xsl:message terminate=\"no\">", 13701 "fall": "<xsl:fallback>", 13702 "num": "<xsl:number value=\"\"/>", 13703 "nam": "<namespace-alias stylesheet-prefix=\"\" result-prefix=\"\"/>", 13704 "pres": "<xsl:preserve-space elements=\"\"/>", 13705 "strip": "<xsl:strip-space elements=\"\"/>", 13706 "proc": "<xsl:processing-instruction name=\"\">", 13707 "sort": "<xsl:sort select=\"\" order=\"\"/>", 13708 13709 "choose+": "xsl:choose>xsl:when+xsl:otherwise", 13710 "xsl": "!!!+xsl:stylesheet[version=1.0 xmlns:xsl=http://www.w3.org/1999/XSL/Transform]>{\n|}" 13711 }, 13712 "snippets": { 13713 "!!!": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 13714 } 13715 }, 13716 13717 "haml": { 13718 "filters": "haml", 13719 "extends": "html", 13720 "profile": "xml" 13721 }, 13722 13723 "scss": { 13724 "extends": "css" 13725 }, 13726 13727 "sass": { 13728 "extends": "css" 13729 }, 13730 13731 "less": { 13732 "extends": "css" 13733 }, 13734 13735 "stylus": { 13736 "extends": "css" 13737 }, 13738 13739 "styl": { 13740 "extends": "stylus" 13741 } 13742 } 13743 , 'system');});