PageRenderTime 136ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 2ms

/static/js/widget/emmet.js

https://github.com/3n1b-com/3n1b.com
JavaScript | 13447 lines | 10168 code | 1034 blank | 2245 comment | 994 complexity | 9c53f3023aa9ca47fb3d29f70ca2d942 MD5 | raw file
  1. // Underscore.js 1.3.3
  2. // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
  3. // Underscore is freely distributable under the MIT license.
  4. // Portions of Underscore are inspired or borrowed from Prototype,
  5. // Oliver Steele's Functional, and John Resig's Micro-Templating.
  6. // For all details and documentation:
  7. // http://documentcloud.github.com/underscore
  8. var _ = (function() {
  9. // Baseline setup
  10. // --------------
  11. // Establish the root object, `window` in the browser, or `global` on the server.
  12. var root = this;
  13. // Save the previous value of the `_` variable.
  14. var previousUnderscore = root._;
  15. // Establish the object that gets returned to break out of a loop iteration.
  16. var breaker = {};
  17. // Save bytes in the minified (but not gzipped) version:
  18. var ArrayProto = Array.prototype,
  19. ObjProto = Object.prototype,
  20. FuncProto = Function.prototype;
  21. // Create quick reference variables for speed access to core prototypes.
  22. var slice = ArrayProto.slice,
  23. unshift = ArrayProto.unshift,
  24. toString = ObjProto.toString,
  25. hasOwnProperty = ObjProto.hasOwnProperty;
  26. // All **ECMAScript 5** native function implementations that we hope to use
  27. // are declared here.
  28. var
  29. nativeForEach = ArrayProto.forEach,
  30. nativeMap = ArrayProto.map,
  31. nativeReduce = ArrayProto.reduce,
  32. nativeReduceRight = ArrayProto.reduceRight,
  33. nativeFilter = ArrayProto.filter,
  34. nativeEvery = ArrayProto.every,
  35. nativeSome = ArrayProto.some,
  36. nativeIndexOf = ArrayProto.indexOf,
  37. nativeLastIndexOf = ArrayProto.lastIndexOf,
  38. nativeIsArray = Array.isArray,
  39. nativeKeys = Object.keys,
  40. nativeBind = FuncProto.bind;
  41. // Create a safe reference to the Underscore object for use below.
  42. var _ = function(obj) {
  43. return new wrapper(obj);
  44. };
  45. // Export the Underscore object for **Node.js**, with
  46. // backwards-compatibility for the old `require()` API. If we're in
  47. // the browser, add `_` as a global object via a string identifier,
  48. // for Closure Compiler "advanced" mode.
  49. if(typeof exports !== 'undefined') {
  50. if(typeof module !== 'undefined' && module.exports) {
  51. exports = module.exports = _;
  52. }
  53. exports._ = _;
  54. } else {
  55. root['_'] = _;
  56. }
  57. // Current version.
  58. _.VERSION = '1.3.3';
  59. // Collection Functions
  60. // --------------------
  61. // The cornerstone, an `each` implementation, aka `forEach`.
  62. // Handles objects with the built-in `forEach`, arrays, and raw objects.
  63. // Delegates to **ECMAScript 5**'s native `forEach` if available.
  64. var each = _.each = _.forEach = function(obj, iterator, context) {
  65. if(obj == null) return;
  66. if(nativeForEach && obj.forEach === nativeForEach) {
  67. obj.forEach(iterator, context);
  68. } else if(obj.length === +obj.length) {
  69. for(var i = 0, l = obj.length; i < l; i++) {
  70. if(i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
  71. }
  72. } else {
  73. for(var key in obj) {
  74. if(_.has(obj, key)) {
  75. if(iterator.call(context, obj[key], key, obj) === breaker) return;
  76. }
  77. }
  78. }
  79. };
  80. // Return the results of applying the iterator to each element.
  81. // Delegates to **ECMAScript 5**'s native `map` if available.
  82. _.map = _.collect = function(obj, iterator, context) {
  83. var results = [];
  84. if(obj == null) return results;
  85. if(nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
  86. each(obj, function(value, index, list) {
  87. results[results.length] = iterator.call(context, value, index, list);
  88. });
  89. if(obj.length === +obj.length) results.length = obj.length;
  90. return results;
  91. };
  92. // **Reduce** builds up a single result from a list of values, aka `inject`,
  93. // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
  94. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
  95. var initial = arguments.length > 2;
  96. if(obj == null) obj = [];
  97. if(nativeReduce && obj.reduce === nativeReduce) {
  98. if(context) iterator = _.bind(iterator, context);
  99. return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
  100. }
  101. each(obj, function(value, index, list) {
  102. if(!initial) {
  103. memo = value;
  104. initial = true;
  105. } else {
  106. memo = iterator.call(context, memo, value, index, list);
  107. }
  108. });
  109. if(!initial) throw new TypeError('Reduce of empty array with no initial value');
  110. return memo;
  111. };
  112. // The right-associative version of reduce, also known as `foldr`.
  113. // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
  114. _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
  115. var initial = arguments.length > 2;
  116. if(obj == null) obj = [];
  117. if(nativeReduceRight && obj.reduceRight === nativeReduceRight) {
  118. if(context) iterator = _.bind(iterator, context);
  119. return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
  120. }
  121. var reversed = _.toArray(obj).reverse();
  122. if(context && !initial) iterator = _.bind(iterator, context);
  123. return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
  124. };
  125. // Return the first value which passes a truth test. Aliased as `detect`.
  126. _.find = _.detect = function(obj, iterator, context) {
  127. var result;
  128. any(obj, function(value, index, list) {
  129. if(iterator.call(context, value, index, list)) {
  130. result = value;
  131. return true;
  132. }
  133. });
  134. return result;
  135. };
  136. // Return all the elements that pass a truth test.
  137. // Delegates to **ECMAScript 5**'s native `filter` if available.
  138. // Aliased as `select`.
  139. _.filter = _.select = function(obj, iterator, context) {
  140. var results = [];
  141. if(obj == null) return results;
  142. if(nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
  143. each(obj, function(value, index, list) {
  144. if(iterator.call(context, value, index, list)) results[results.length] = value;
  145. });
  146. return results;
  147. };
  148. // Return all the elements for which a truth test fails.
  149. _.reject = function(obj, iterator, context) {
  150. var results = [];
  151. if(obj == null) return results;
  152. each(obj, function(value, index, list) {
  153. if(!iterator.call(context, value, index, list)) results[results.length] = value;
  154. });
  155. return results;
  156. };
  157. // Determine whether all of the elements match a truth test.
  158. // Delegates to **ECMAScript 5**'s native `every` if available.
  159. // Aliased as `all`.
  160. _.every = _.all = function(obj, iterator, context) {
  161. var result = true;
  162. if(obj == null) return result;
  163. if(nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
  164. each(obj, function(value, index, list) {
  165. if(!(result = result && iterator.call(context, value, index, list))) return breaker;
  166. });
  167. return !!result;
  168. };
  169. // Determine if at least one element in the object matches a truth test.
  170. // Delegates to **ECMAScript 5**'s native `some` if available.
  171. // Aliased as `any`.
  172. var any = _.some = _.any = function(obj, iterator, context) {
  173. iterator || (iterator = _.identity);
  174. var result = false;
  175. if(obj == null) return result;
  176. if(nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
  177. each(obj, function(value, index, list) {
  178. if(result || (result = iterator.call(context, value, index, list))) return breaker;
  179. });
  180. return !!result;
  181. };
  182. // Determine if a given value is included in the array or object using `===`.
  183. // Aliased as `contains`.
  184. _.include = _.contains = function(obj, target) {
  185. var found = false;
  186. if(obj == null) return found;
  187. if(nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
  188. found = any(obj, function(value) {
  189. return value === target;
  190. });
  191. return found;
  192. };
  193. // Invoke a method (with arguments) on every item in a collection.
  194. _.invoke = function(obj, method) {
  195. var args = slice.call(arguments, 2);
  196. return _.map(obj, function(value) {
  197. return(_.isFunction(method) ? method || value : value[method]).apply(value, args);
  198. });
  199. };
  200. // Convenience version of a common use case of `map`: fetching a property.
  201. _.pluck = function(obj, key) {
  202. return _.map(obj, function(value) {
  203. return value[key];
  204. });
  205. };
  206. // Return the maximum element or (element-based computation).
  207. _.max = function(obj, iterator, context) {
  208. if(!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj);
  209. if(!iterator && _.isEmpty(obj)) return -Infinity;
  210. var result = {
  211. computed: -Infinity
  212. };
  213. each(obj, function(value, index, list) {
  214. var computed = iterator ? iterator.call(context, value, index, list) : value;
  215. computed >= result.computed && (result = {
  216. value: value,
  217. computed: computed
  218. });
  219. });
  220. return result.value;
  221. };
  222. // Return the minimum element (or element-based computation).
  223. _.min = function(obj, iterator, context) {
  224. if(!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj);
  225. if(!iterator && _.isEmpty(obj)) return Infinity;
  226. var result = {
  227. computed: Infinity
  228. };
  229. each(obj, function(value, index, list) {
  230. var computed = iterator ? iterator.call(context, value, index, list) : value;
  231. computed < result.computed && (result = {
  232. value: value,
  233. computed: computed
  234. });
  235. });
  236. return result.value;
  237. };
  238. // Shuffle an array.
  239. _.shuffle = function(obj) {
  240. var shuffled = [],
  241. rand;
  242. each(obj, function(value, index, list) {
  243. rand = Math.floor(Math.random() * (index + 1));
  244. shuffled[index] = shuffled[rand];
  245. shuffled[rand] = value;
  246. });
  247. return shuffled;
  248. };
  249. // Sort the object's values by a criterion produced by an iterator.
  250. _.sortBy = function(obj, val, context) {
  251. var iterator = _.isFunction(val) ? val : function(obj) {
  252. return obj[val];
  253. };
  254. return _.pluck(_.map(obj, function(value, index, list) {
  255. return {
  256. value: value,
  257. criteria: iterator.call(context, value, index, list)
  258. };
  259. }).sort(function(left, right) {
  260. var a = left.criteria,
  261. b = right.criteria;
  262. if(a === void 0) return 1;
  263. if(b === void 0) return -1;
  264. return a < b ? -1 : a > b ? 1 : 0;
  265. }), 'value');
  266. };
  267. // Groups the object's values by a criterion. Pass either a string attribute
  268. // to group by, or a function that returns the criterion.
  269. _.groupBy = function(obj, val) {
  270. var result = {};
  271. var iterator = _.isFunction(val) ? val : function(obj) {
  272. return obj[val];
  273. };
  274. each(obj, function(value, index) {
  275. var key = iterator(value, index);
  276. (result[key] || (result[key] = [])).push(value);
  277. });
  278. return result;
  279. };
  280. // Use a comparator function to figure out at what index an object should
  281. // be inserted so as to maintain order. Uses binary search.
  282. _.sortedIndex = function(array, obj, iterator) {
  283. iterator || (iterator = _.identity);
  284. var low = 0,
  285. high = array.length;
  286. while(low < high) {
  287. var mid = (low + high) >> 1;
  288. iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
  289. }
  290. return low;
  291. };
  292. // Safely convert anything iterable into a real, live array.
  293. _.toArray = function(obj) {
  294. if(!obj) return [];
  295. if(_.isArray(obj)) return slice.call(obj);
  296. if(_.isArguments(obj)) return slice.call(obj);
  297. if(obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
  298. return _.values(obj);
  299. };
  300. // Return the number of elements in an object.
  301. _.size = function(obj) {
  302. return _.isArray(obj) ? obj.length : _.keys(obj).length;
  303. };
  304. // Array Functions
  305. // ---------------
  306. // Get the first element of an array. Passing **n** will return the first N
  307. // values in the array. Aliased as `head` and `take`. The **guard** check
  308. // allows it to work with `_.map`.
  309. _.first = _.head = _.take = function(array, n, guard) {
  310. return(n != null) && !guard ? slice.call(array, 0, n) : array[0];
  311. };
  312. // Returns everything but the last entry of the array. Especcialy useful on
  313. // the arguments object. Passing **n** will return all the values in
  314. // the array, excluding the last N. The **guard** check allows it to work with
  315. // `_.map`.
  316. _.initial = function(array, n, guard) {
  317. return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
  318. };
  319. // Get the last element of an array. Passing **n** will return the last N
  320. // values in the array. The **guard** check allows it to work with `_.map`.
  321. _.last = function(array, n, guard) {
  322. if((n != null) && !guard) {
  323. return slice.call(array, Math.max(array.length - n, 0));
  324. } else {
  325. return array[array.length - 1];
  326. }
  327. };
  328. // Returns everything but the first entry of the array. Aliased as `tail`.
  329. // Especially useful on the arguments object. Passing an **index** will return
  330. // the rest of the values in the array from that index onward. The **guard**
  331. // check allows it to work with `_.map`.
  332. _.rest = _.tail = function(array, index, guard) {
  333. return slice.call(array, (index == null) || guard ? 1 : index);
  334. };
  335. // Trim out all falsy values from an array.
  336. _.compact = function(array) {
  337. return _.filter(array, function(value) {
  338. return !!value;
  339. });
  340. };
  341. // Return a completely flattened version of an array.
  342. _.flatten = function(array, shallow) {
  343. return _.reduce(array, function(memo, value) {
  344. if(_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
  345. memo[memo.length] = value;
  346. return memo;
  347. }, []);
  348. };
  349. // Return a version of the array that does not contain the specified value(s).
  350. _.without = function(array) {
  351. return _.difference(array, slice.call(arguments, 1));
  352. };
  353. // Produce a duplicate-free version of the array. If the array has already
  354. // been sorted, you have the option of using a faster algorithm.
  355. // Aliased as `unique`.
  356. _.uniq = _.unique = function(array, isSorted, iterator) {
  357. var initial = iterator ? _.map(array, iterator) : array;
  358. var results = [];
  359. // The `isSorted` flag is irrelevant if the array only contains two elements.
  360. if(array.length < 3) isSorted = true;
  361. _.reduce(initial, function(memo, value, index) {
  362. if(isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
  363. memo.push(value);
  364. results.push(array[index]);
  365. }
  366. return memo;
  367. }, []);
  368. return results;
  369. };
  370. // Produce an array that contains the union: each distinct element from all of
  371. // the passed-in arrays.
  372. _.union = function() {
  373. return _.uniq(_.flatten(arguments, true));
  374. };
  375. // Produce an array that contains every item shared between all the
  376. // passed-in arrays. (Aliased as "intersect" for back-compat.)
  377. _.intersection = _.intersect = function(array) {
  378. var rest = slice.call(arguments, 1);
  379. return _.filter(_.uniq(array), function(item) {
  380. return _.every(rest, function(other) {
  381. return _.indexOf(other, item) >= 0;
  382. });
  383. });
  384. };
  385. // Take the difference between one array and a number of other arrays.
  386. // Only the elements present in just the first array will remain.
  387. _.difference = function(array) {
  388. var rest = _.flatten(slice.call(arguments, 1), true);
  389. return _.filter(array, function(value) {
  390. return !_.include(rest, value);
  391. });
  392. };
  393. // Zip together multiple lists into a single array -- elements that share
  394. // an index go together.
  395. _.zip = function() {
  396. var args = slice.call(arguments);
  397. var length = _.max(_.pluck(args, 'length'));
  398. var results = new Array(length);
  399. for(var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
  400. return results;
  401. };
  402. // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
  403. // we need this function. Return the position of the first occurrence of an
  404. // item in an array, or -1 if the item is not included in the array.
  405. // Delegates to **ECMAScript 5**'s native `indexOf` if available.
  406. // If the array is large and already in sort order, pass `true`
  407. // for **isSorted** to use binary search.
  408. _.indexOf = function(array, item, isSorted) {
  409. if(array == null) return -1;
  410. var i, l;
  411. if(isSorted) {
  412. i = _.sortedIndex(array, item);
  413. return array[i] === item ? i : -1;
  414. }
  415. if(nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
  416. for(i = 0, l = array.length; i < l; i++) if(i in array && array[i] === item) return i;
  417. return -1;
  418. };
  419. // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
  420. _.lastIndexOf = function(array, item) {
  421. if(array == null) return -1;
  422. if(nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
  423. var i = array.length;
  424. while(i--) if(i in array && array[i] === item) return i;
  425. return -1;
  426. };
  427. // Generate an integer Array containing an arithmetic progression. A port of
  428. // the native Python `range()` function. See
  429. // [the Python documentation](http://docs.python.org/library/functions.html#range).
  430. _.range = function(start, stop, step) {
  431. if(arguments.length <= 1) {
  432. stop = start || 0;
  433. start = 0;
  434. }
  435. step = arguments[2] || 1;
  436. var len = Math.max(Math.ceil((stop - start) / step), 0);
  437. var idx = 0;
  438. var range = new Array(len);
  439. while(idx < len) {
  440. range[idx++] = start;
  441. start += step;
  442. }
  443. return range;
  444. };
  445. // Function (ahem) Functions
  446. // ------------------
  447. // Reusable constructor function for prototype setting.
  448. var ctor = function() {};
  449. // Create a function bound to a given object (assigning `this`, and arguments,
  450. // optionally). Binding with arguments is also known as `curry`.
  451. // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
  452. // We check for `func.bind` first, to fail fast when `func` is undefined.
  453. _.bind = function bind(func, context) {
  454. var bound, args;
  455. if(func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
  456. if(!_.isFunction(func)) throw new TypeError;
  457. args = slice.call(arguments, 2);
  458. return bound = function() {
  459. if(!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
  460. ctor.prototype = func.prototype;
  461. var self = new ctor;
  462. var result = func.apply(self, args.concat(slice.call(arguments)));
  463. if(Object(result) === result) return result;
  464. return self;
  465. };
  466. };
  467. // Bind all of an object's methods to that object. Useful for ensuring that
  468. // all callbacks defined on an object belong to it.
  469. _.bindAll = function(obj) {
  470. var funcs = slice.call(arguments, 1);
  471. if(funcs.length == 0) funcs = _.functions(obj);
  472. each(funcs, function(f) {
  473. obj[f] = _.bind(obj[f], obj);
  474. });
  475. return obj;
  476. };
  477. // Memoize an expensive function by storing its results.
  478. _.memoize = function(func, hasher) {
  479. var memo = {};
  480. hasher || (hasher = _.identity);
  481. return function() {
  482. var key = hasher.apply(this, arguments);
  483. return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
  484. };
  485. };
  486. // Delays a function for the given number of milliseconds, and then calls
  487. // it with the arguments supplied.
  488. _.delay = function(func, wait) {
  489. var args = slice.call(arguments, 2);
  490. return setTimeout(function() {
  491. return func.apply(null, args);
  492. }, wait);
  493. };
  494. // Defers a function, scheduling it to run after the current call stack has
  495. // cleared.
  496. _.defer = function(func) {
  497. return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
  498. };
  499. // Returns a function, that, when invoked, will only be triggered at most once
  500. // during a given window of time.
  501. _.throttle = function(func, wait) {
  502. var context, args, timeout, throttling, more, result;
  503. var whenDone = _.debounce(function() {
  504. more = throttling = false;
  505. }, wait);
  506. return function() {
  507. context = this;
  508. args = arguments;
  509. var later = function() {
  510. timeout = null;
  511. if(more) func.apply(context, args);
  512. whenDone();
  513. };
  514. if(!timeout) timeout = setTimeout(later, wait);
  515. if(throttling) {
  516. more = true;
  517. } else {
  518. result = func.apply(context, args);
  519. }
  520. whenDone();
  521. throttling = true;
  522. return result;
  523. };
  524. };
  525. // Returns a function, that, as long as it continues to be invoked, will not
  526. // be triggered. The function will be called after it stops being called for
  527. // N milliseconds. If `immediate` is passed, trigger the function on the
  528. // leading edge, instead of the trailing.
  529. _.debounce = function(func, wait, immediate) {
  530. var timeout;
  531. return function() {
  532. var context = this,
  533. args = arguments;
  534. var later = function() {
  535. timeout = null;
  536. if(!immediate) func.apply(context, args);
  537. };
  538. if(immediate && !timeout) func.apply(context, args);
  539. clearTimeout(timeout);
  540. timeout = setTimeout(later, wait);
  541. };
  542. };
  543. // Returns a function that will be executed at most one time, no matter how
  544. // often you call it. Useful for lazy initialization.
  545. _.once = function(func) {
  546. var ran = false,
  547. memo;
  548. return function() {
  549. if(ran) return memo;
  550. ran = true;
  551. return memo = func.apply(this, arguments);
  552. };
  553. };
  554. // Returns the first function passed as an argument to the second,
  555. // allowing you to adjust arguments, run code before and after, and
  556. // conditionally execute the original function.
  557. _.wrap = function(func, wrapper) {
  558. return function() {
  559. var args = [func].concat(slice.call(arguments, 0));
  560. return wrapper.apply(this, args);
  561. };
  562. };
  563. // Returns a function that is the composition of a list of functions, each
  564. // consuming the return value of the function that follows.
  565. _.compose = function() {
  566. var funcs = arguments;
  567. return function() {
  568. var args = arguments;
  569. for(var i = funcs.length - 1; i >= 0; i--) {
  570. args = [funcs[i].apply(this, args)];
  571. }
  572. return args[0];
  573. };
  574. };
  575. // Returns a function that will only be executed after being called N times.
  576. _.after = function(times, func) {
  577. if(times <= 0) return func();
  578. return function() {
  579. if(--times < 1) {
  580. return func.apply(this, arguments);
  581. }
  582. };
  583. };
  584. // Object Functions
  585. // ----------------
  586. // Retrieve the names of an object's properties.
  587. // Delegates to **ECMAScript 5**'s native `Object.keys`
  588. _.keys = nativeKeys ||
  589. function(obj) {
  590. if(obj !== Object(obj)) throw new TypeError('Invalid object');
  591. var keys = [];
  592. for(var key in obj) if(_.has(obj, key)) keys[keys.length] = key;
  593. return keys;
  594. };
  595. // Retrieve the values of an object's properties.
  596. _.values = function(obj) {
  597. return _.map(obj, _.identity);
  598. };
  599. // Return a sorted list of the function names available on the object.
  600. // Aliased as `methods`
  601. _.functions = _.methods = function(obj) {
  602. var names = [];
  603. for(var key in obj) {
  604. if(_.isFunction(obj[key])) names.push(key);
  605. }
  606. return names.sort();
  607. };
  608. // Extend a given object with all the properties in passed-in object(s).
  609. _.extend = function(obj) {
  610. each(slice.call(arguments, 1), function(source) {
  611. for(var prop in source) {
  612. obj[prop] = source[prop];
  613. }
  614. });
  615. return obj;
  616. };
  617. // Return a copy of the object only containing the whitelisted properties.
  618. _.pick = function(obj) {
  619. var result = {};
  620. each(_.flatten(slice.call(arguments, 1)), function(key) {
  621. if(key in obj) result[key] = obj[key];
  622. });
  623. return result;
  624. };
  625. // Fill in a given object with default properties.
  626. _.defaults = function(obj) {
  627. each(slice.call(arguments, 1), function(source) {
  628. for(var prop in source) {
  629. if(obj[prop] == null) obj[prop] = source[prop];
  630. }
  631. });
  632. return obj;
  633. };
  634. // Create a (shallow-cloned) duplicate of an object.
  635. _.clone = function(obj) {
  636. if(!_.isObject(obj)) return obj;
  637. return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  638. };
  639. // Invokes interceptor with the obj, and then returns obj.
  640. // The primary purpose of this method is to "tap into" a method chain, in
  641. // order to perform operations on intermediate results within the chain.
  642. _.tap = function(obj, interceptor) {
  643. interceptor(obj);
  644. return obj;
  645. };
  646. // Internal recursive comparison function.
  647. function eq(a, b, stack) {
  648. // Identical objects are equal. `0 === -0`, but they aren't identical.
  649. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
  650. if(a === b) return a !== 0 || 1 / a == 1 / b;
  651. // A strict comparison is necessary because `null == undefined`.
  652. if(a == null || b == null) return a === b;
  653. // Unwrap any wrapped objects.
  654. if(a._chain) a = a._wrapped;
  655. if(b._chain) b = b._wrapped;
  656. // Invoke a custom `isEqual` method if one is provided.
  657. if(a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
  658. if(b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
  659. // Compare `[[Class]]` names.
  660. var className = toString.call(a);
  661. if(className != toString.call(b)) return false;
  662. switch(className) {
  663. // Strings, numbers, dates, and booleans are compared by value.
  664. case '[object String]':
  665. // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
  666. // equivalent to `new String("5")`.
  667. return a == String(b);
  668. case '[object Number]':
  669. // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
  670. // other numeric values.
  671. return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
  672. case '[object Date]':
  673. case '[object Boolean]':
  674. // Coerce dates and booleans to numeric primitive values. Dates are compared by their
  675. // millisecond representations. Note that invalid dates with millisecond representations
  676. // of `NaN` are not equivalent.
  677. return +a == +b;
  678. // RegExps are compared by their source patterns and flags.
  679. case '[object RegExp]':
  680. return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase;
  681. }
  682. if(typeof a != 'object' || typeof b != 'object') return false;
  683. // Assume equality for cyclic structures. The algorithm for detecting cyclic
  684. // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
  685. var length = stack.length;
  686. while(length--) {
  687. // Linear search. Performance is inversely proportional to the number of
  688. // unique nested structures.
  689. if(stack[length] == a) return true;
  690. }
  691. // Add the first object to the stack of traversed objects.
  692. stack.push(a);
  693. var size = 0,
  694. result = true;
  695. // Recursively compare objects and arrays.
  696. if(className == '[object Array]') {
  697. // Compare array lengths to determine if a deep comparison is necessary.
  698. size = a.length;
  699. result = size == b.length;
  700. if(result) {
  701. // Deep compare the contents, ignoring non-numeric properties.
  702. while(size--) {
  703. // Ensure commutative equality for sparse arrays.
  704. if(!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
  705. }
  706. }
  707. } else {
  708. // Objects with different constructors are not equivalent.
  709. if('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
  710. // Deep compare objects.
  711. for(var key in a) {
  712. if(_.has(a, key)) {
  713. // Count the expected number of properties.
  714. size++;
  715. // Deep compare each member.
  716. if(!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
  717. }
  718. }
  719. // Ensure that both objects contain the same number of properties.
  720. if(result) {
  721. for(key in b) {
  722. if(_.has(b, key) && !(size--)) break;
  723. }
  724. result = !size;
  725. }
  726. }
  727. // Remove the first object from the stack of traversed objects.
  728. stack.pop();
  729. return result;
  730. }
  731. // Perform a deep comparison to check if two objects are equal.
  732. _.isEqual = function(a, b) {
  733. return eq(a, b, []);
  734. };
  735. // Is a given array, string, or object empty?
  736. // An "empty" object has no enumerable own-properties.
  737. _.isEmpty = function(obj) {
  738. if(obj == null) return true;
  739. if(_.isArray(obj) || _.isString(obj)) return obj.length === 0;
  740. for(var key in obj) if(_.has(obj, key)) return false;
  741. return true;
  742. };
  743. // Is a given value a DOM element?
  744. _.isElement = function(obj) {
  745. return !!(obj && obj.nodeType == 1);
  746. };
  747. // Is a given value an array?
  748. // Delegates to ECMA5's native Array.isArray
  749. _.isArray = nativeIsArray ||
  750. function(obj) {
  751. return toString.call(obj) == '[object Array]';
  752. };
  753. // Is a given variable an object?
  754. _.isObject = function(obj) {
  755. return obj === Object(obj);
  756. };
  757. // Is a given variable an arguments object?
  758. _.isArguments = function(obj) {
  759. return toString.call(obj) == '[object Arguments]';
  760. };
  761. if(!_.isArguments(arguments)) {
  762. _.isArguments = function(obj) {
  763. return !!(obj && _.has(obj, 'callee'));
  764. };
  765. }
  766. // Is a given value a function?
  767. _.isFunction = function(obj) {
  768. return toString.call(obj) == '[object Function]';
  769. };
  770. // Is a given value a string?
  771. _.isString = function(obj) {
  772. return toString.call(obj) == '[object String]';
  773. };
  774. // Is a given value a number?
  775. _.isNumber = function(obj) {
  776. return toString.call(obj) == '[object Number]';
  777. };
  778. // Is a given object a finite number?
  779. _.isFinite = function(obj) {
  780. return _.isNumber(obj) && isFinite(obj);
  781. };
  782. // Is the given value `NaN`?
  783. _.isNaN = function(obj) {
  784. // `NaN` is the only value for which `===` is not reflexive.
  785. return obj !== obj;
  786. };
  787. // Is a given value a boolean?
  788. _.isBoolean = function(obj) {
  789. return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
  790. };
  791. // Is a given value a date?
  792. _.isDate = function(obj) {
  793. return toString.call(obj) == '[object Date]';
  794. };
  795. // Is the given value a regular expression?
  796. _.isRegExp = function(obj) {
  797. return toString.call(obj) == '[object RegExp]';
  798. };
  799. // Is a given value equal to null?
  800. _.isNull = function(obj) {
  801. return obj === null;
  802. };
  803. // Is a given variable undefined?
  804. _.isUndefined = function(obj) {
  805. return obj === void 0;
  806. };
  807. // Has own property?
  808. _.has = function(obj, key) {
  809. return hasOwnProperty.call(obj, key);
  810. };
  811. // Utility Functions
  812. // -----------------
  813. // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  814. // previous owner. Returns a reference to the Underscore object.
  815. _.noConflict = function() {
  816. root._ = previousUnderscore;
  817. return this;
  818. };
  819. // Keep the identity function around for default iterators.
  820. _.identity = function(value) {
  821. return value;
  822. };
  823. // Run a function **n** times.
  824. _.times = function(n, iterator, context) {
  825. for(var i = 0; i < n; i++) iterator.call(context, i);
  826. };
  827. // Escape a string for HTML interpolation.
  828. _.escape = function(string) {
  829. return('' + string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g, '&#x2F;');
  830. };
  831. // If the value of the named property is a function then invoke it;
  832. // otherwise, return it.
  833. _.result = function(object, property) {
  834. if(object == null) return null;
  835. var value = object[property];
  836. return _.isFunction(value) ? value.call(object) : value;
  837. };
  838. // Add your own custom functions to the Underscore object, ensuring that
  839. // they're correctly added to the OOP wrapper as well.
  840. _.mixin = function(obj) {
  841. each(_.functions(obj), function(name) {
  842. addToWrapper(name, _[name] = obj[name]);
  843. });
  844. };
  845. // Generate a unique integer id (unique within the entire client session).
  846. // Useful for temporary DOM ids.
  847. var idCounter = 0;
  848. _.uniqueId = function(prefix) {
  849. var id = idCounter++;
  850. return prefix ? prefix + id : id;
  851. };
  852. // By default, Underscore uses ERB-style template delimiters, change the
  853. // following template settings to use alternative delimiters.
  854. _.templateSettings = {
  855. evaluate: /<%([\s\S]+?)%>/g,
  856. interpolate: /<%=([\s\S]+?)%>/g,
  857. escape: /<%-([\s\S]+?)%>/g
  858. };
  859. // When customizing `templateSettings`, if you don't want to define an
  860. // interpolation, evaluation or escaping regex, we need one that is
  861. // guaranteed not to match.
  862. var noMatch = /.^/;
  863. // Certain characters need to be escaped so that they can be put into a
  864. // string literal.
  865. var escapes = {
  866. '\\': '\\',
  867. "'": "'",
  868. 'r': '\r',
  869. 'n': '\n',
  870. 't': '\t',
  871. 'u2028': '\u2028',
  872. 'u2029': '\u2029'
  873. };
  874. for(var p in escapes) escapes[escapes[p]] = p;
  875. var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
  876. var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
  877. // Within an interpolation, evaluation, or escaping, remove HTML escaping
  878. // that had been previously added.
  879. var unescape = function(code) {
  880. return code.replace(unescaper, function(match, escape) {
  881. return escapes[escape];
  882. });
  883. };
  884. // JavaScript micro-templating, similar to John Resig's implementation.
  885. // Underscore templating handles arbitrary delimiters, preserves whitespace,
  886. // and correctly escapes quotes within interpolated code.
  887. _.template = function(text, data, settings) {
  888. settings = _.defaults(settings || {}, _.templateSettings);
  889. // Compile the template source, taking care to escape characters that
  890. // cannot be included in a string literal and then unescape them in code
  891. // blocks.
  892. var source = "__p+='" + text.replace(escaper, function(match) {
  893. return '\\' + escapes[match];
  894. }).replace(settings.escape || noMatch, function(match, code) {
  895. return "'+\n_.escape(" + unescape(code) + ")+\n'";
  896. }).replace(settings.interpolate || noMatch, function(match, code) {
  897. return "'+\n(" + unescape(code) + ")+\n'";
  898. }).replace(settings.evaluate || noMatch, function(match, code) {
  899. return "';\n" + unescape(code) + "\n;__p+='";
  900. }) + "';\n";
  901. // If a variable is not specified, place data values in local scope.
  902. if(!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
  903. source = "var __p='';" + "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n";
  904. var render = new Function(settings.variable || 'obj', '_', source);
  905. if(data) return render(data, _);
  906. var template = function(data) {
  907. return render.call(this, data, _);
  908. };
  909. // Provide the compiled function source as a convenience for build time
  910. // precompilation.
  911. template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
  912. return template;
  913. };
  914. // Add a "chain" function, which will delegate to the wrapper.
  915. _.chain = function(obj) {
  916. return _(obj).chain();
  917. };
  918. // The OOP Wrapper
  919. // ---------------
  920. // If Underscore is called as a function, it returns a wrapped object that
  921. // can be used OO-style. This wrapper holds altered versions of all the
  922. // underscore functions. Wrapped objects may be chained.
  923. var wrapper = function(obj) {
  924. this._wrapped = obj;
  925. };
  926. // Expose `wrapper.prototype` as `_.prototype`
  927. _.prototype = wrapper.prototype;
  928. // Helper function to continue chaining intermediate results.
  929. var result = function(obj, chain) {
  930. return chain ? _(obj).chain() : obj;
  931. };
  932. // A method to easily add functions to the OOP wrapper.
  933. var addToWrapper = function(name, func) {
  934. wrapper.prototype[name] = function() {
  935. var args = slice.call(arguments);
  936. unshift.call(args, this._wrapped);
  937. return result(func.apply(_, args), this._chain);
  938. };
  939. };
  940. // Add all of the Underscore functions to the wrapper object.
  941. _.mixin(_);
  942. // Add all mutator Array functions to the wrapper.
  943. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  944. var method = ArrayProto[name];
  945. wrapper.prototype[name] = function() {
  946. var wrapped = this._wrapped;
  947. method.apply(wrapped, arguments);
  948. var length = wrapped.length;
  949. if((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
  950. return result(wrapped, this._chain);
  951. };
  952. });
  953. // Add all accessor Array functions to the wrapper.
  954. each(['concat', 'join', 'slice'], function(name) {
  955. var method = ArrayProto[name];
  956. wrapper.prototype[name] = function() {
  957. return result(method.apply(this._wrapped, arguments), this._chain);
  958. };
  959. });
  960. // Start chaining a wrapped Underscore object.
  961. wrapper.prototype.chain = function() {
  962. this._chain = true;
  963. return this;
  964. };
  965. // Extracts the result from a wrapped and chained object.
  966. wrapper.prototype.value = function() {
  967. return this._wrapped;
  968. };
  969. return _;
  970. }).call({});
  971. /**
  972. * Core Emmet object, available in global scope
  973. */
  974. var emmet = (function(global) {
  975. var defaultSyntax = 'html';
  976. var defaultProfile = 'plain';
  977. if(typeof _ == 'undefined') {
  978. try {
  979. // avoid collisions with RequireJS loader
  980. // also, JS obfuscators tends to translate
  981. // a["name"] to a.name, which also breaks RequireJS
  982. _ = global[['require'][0]]('underscore'); // node.js
  983. } catch(e) {}
  984. }
  985. if(typeof _ == 'undefined') {
  986. throw 'Cannot access to Underscore.js lib';
  987. }
  988. /** List of registered modules */
  989. var modules = {
  990. _: _
  991. };
  992. /**
  993. * Shared empty constructor function to aid in prototype-chain creation.
  994. */
  995. var ctor = function() {};
  996. /**
  997. * Helper function to correctly set up the prototype chain, for subclasses.
  998. * Similar to `goog.inherits`, but uses a hash of prototype properties and
  999. * class properties to be extended.
  1000. * Took it from Backbone.
  1001. * @param {Object} parent
  1002. * @param {Object} protoProps
  1003. * @param {Object} staticProps
  1004. * @returns {Object}
  1005. */
  1006. function inherits(parent, protoProps, staticProps) {
  1007. var child;
  1008. // The constructor function for the new subclass is either defined by
  1009. // you (the "constructor" property in your `extend` definition), or
  1010. // defaulted by us to simply call the parent's constructor.
  1011. if(protoProps && protoProps.hasOwnProperty('constructor')) {
  1012. child = protoProps.constructor;
  1013. } else {
  1014. child = function() {
  1015. parent.apply(this, arguments);
  1016. };
  1017. }
  1018. // Inherit class (static) properties from parent.
  1019. _.extend(child, parent);
  1020. // Set the prototype chain to inherit from `parent`, without calling
  1021. // `parent`'s constructor function.
  1022. ctor.prototype = parent.prototype;
  1023. child.prototype = new ctor();
  1024. // Add prototype properties (instance properties) to the subclass,
  1025. // if supplied.
  1026. if(protoProps) _.extend(child.prototype, protoProps);
  1027. // Add static properties to the constructor function, if supplied.
  1028. if(staticProps) _.extend(child, staticProps);
  1029. // Correctly set child's `prototype.constructor`.
  1030. child.prototype.constructor = child;
  1031. // Set a convenience property in case the parent's prototype is needed
  1032. // later.
  1033. child.__super__ = parent.prototype;
  1034. return child;
  1035. };
  1036. /**
  1037. * @type Function Function that loads module definition if it's not defined
  1038. */
  1039. var moduleLoader = null;
  1040. /**
  1041. * Generic Emmet module loader (actually, it doesn’t load anything, just
  1042. * returns module reference). Not using `require` name to avoid conflicts
  1043. * with Node.js and RequireJS
  1044. */
  1045. function r(name) {
  1046. if(!(name in modules) && moduleLoader) moduleLoader(name);
  1047. return modules[name];
  1048. }
  1049. return {
  1050. /**
  1051. * Simple, AMD-like module definition. The module will be added into
  1052. * <code>emmet</code> object and will be available via
  1053. * <code>emmet.require(name)</code> or <code>emmet[name]</code>
  1054. * @param {String} name
  1055. * @param {Function} factory
  1056. * @memberOf emmet
  1057. */
  1058. define: function(name, factory) {
  1059. // do not let redefine existing properties
  1060. if(!(name in modules)) {
  1061. modules[name] = _.isFunction(factory) ? this.exec(factory) : factory;
  1062. }
  1063. },
  1064. /**
  1065. * Returns reference to Emmet module
  1066. * @param {String} name Module name
  1067. */
  1068. require: r,
  1069. /**
  1070. * Helper method that just executes passed function but with all
  1071. * important arguments like 'require' and '_'
  1072. * @param {Function} fn
  1073. * @param {Object} context Execution context
  1074. */
  1075. exec: function(fn, context) {
  1076. return fn.call(context || global, _.bind(r, this), _, this);
  1077. },
  1078. /**
  1079. * The self-propagating extend function for classes.
  1080. * Took it from Backbone
  1081. * @param {Object} protoProps
  1082. * @param {Object} classProps
  1083. * @returns {Object}
  1084. */
  1085. extend: function(protoProps, classProps) {
  1086. var child = inherits(this, protoProps, classProps);
  1087. child.extend = this.extend;
  1088. // a hack required to WSH inherit `toString` method
  1089. if(protoProps.hasOwnProperty('toString')) child.prototype.toString = protoProps.toString;
  1090. return child;
  1091. },
  1092. /**
  1093. * The essential function that expands Emmet abbreviation
  1094. * @param {String} abbr Abbreviation to parse
  1095. * @param {String} syntax Abbreviation's context syntax
  1096. * @param {String} profile Output profile (or its name)
  1097. * @param {Object} contextNode Contextual node where abbreviation is
  1098. * written
  1099. * @return {String}
  1100. */
  1101. expandAbbreviation: function(abbr, syntax, profile, contextNode) {
  1102. if(!abbr) return '';
  1103. syntax = syntax || defaultSyntax;
  1104. profile = profile || defaultProfile;
  1105. var filters = r('filters');
  1106. var parser = r('abbreviationParser');
  1107. profile = r('profile').get(profile, syntax);
  1108. r('tabStops').resetTabstopIndex();
  1109. var data = filters.extractFromAbbreviation(abbr);
  1110. var outputTree = parser.parse(data[0], {
  1111. syntax: syntax,
  1112. contextNode: contextNode
  1113. });
  1114. var filtersList = filters.composeList(syntax, profile, data[1]);
  1115. filters.apply(outputTree, filtersList, profile);
  1116. return outputTree.toString();
  1117. // return this.require('utils').replaceVariables(outputTree.toString());
  1118. },
  1119. /**
  1120. * Returns default syntax name used in abbreviation engine
  1121. * @returns {String}
  1122. */
  1123. defaultSyntax: function() {
  1124. return defaultSyntax;
  1125. },
  1126. /**
  1127. * Returns default profile name used in abbreviation engine
  1128. * @returns {String}
  1129. */
  1130. defaultProfile: function() {
  1131. return defaultProfile;
  1132. },
  1133. /**
  1134. * Log message into console if it exists
  1135. */
  1136. log: function() {
  1137. if(global.console && global.console.log) global.console.log.apply(global.console, arguments);
  1138. },
  1139. /**
  1140. * Setups function that should synchronously load undefined modules
  1141. * @param {Function} fn
  1142. */
  1143. setModuleLoader: function(fn) {
  1144. moduleLoader = fn;
  1145. }
  1146. };
  1147. })(this);
  1148. // export core for Node.JS
  1149. if(typeof exports !== 'undefined') {
  1150. if(typeof module !== 'undefined' && module.exports) {
  1151. exports = module.exports = emmet;
  1152. }
  1153. exports.emmet = emmet;
  1154. }
  1155. /**
  1156. * Emmet abbreviation parser.
  1157. * Takes string abbreviation and recursively parses it into a tree. The parsed
  1158. * tree can be transformed into a string representation with
  1159. * <code>toString()</code> method. Note that string representation is defined
  1160. * by custom processors (called <i>filters</i>), not by abbreviation parser
  1161. * itself.
  1162. *
  1163. * This module can be extended with custom pre-/post-processors to shape-up
  1164. * final tree or its representation. Actually, many features of abbreviation
  1165. * engine are defined in other modules as tree processors
  1166. *
  1167. *
  1168. * @author Sergey Chikuyonok (serge.che@gmail.com)
  1169. * @link http://chikuyonok.ru
  1170. * @memberOf __abbreviationParser
  1171. * @constructor
  1172. * @param {Function} require
  1173. * @param {Underscore} _
  1174. */
  1175. emmet.define('abbreviationParser', function(require, _) {
  1176. var reValidName = /^[\w\-\$\:@\!]+\+?$/i;
  1177. var reWord = /[\w\-:\$]/;
  1178. var pairs = {
  1179. '[': ']',
  1180. '(': ')',
  1181. '{': '}'
  1182. };
  1183. var spliceFn = Array.prototype.splice;
  1184. var preprocessors = [];
  1185. var postprocessors = [];
  1186. var outputProcessors = [];
  1187. /**
  1188. * @type AbbreviationNode
  1189. */
  1190. function AbbreviationNode(parent) { /** @type AbbreviationNode */
  1191. this.parent = null;
  1192. this.children = [];
  1193. this._attributes = [];
  1194. /** @type String Raw abbreviation for current node */
  1195. this.abbreviation = '';
  1196. this.counter = 1;
  1197. this._name = null;
  1198. this._text = '';
  1199. this.repeatCount = 1;
  1200. this.hasImplicitRepeat = false;
  1201. /** Custom data dictionary */
  1202. this._data = {};
  1203. // output properties
  1204. this.start = '';
  1205. this.end = '';
  1206. this.content = '';
  1207. this.padding = '';
  1208. }
  1209. AbbreviationNode.prototype = {
  1210. /**
  1211. * Adds passed node as child or creates new child
  1212. * @param {AbbreviationNode} child
  1213. * @param {Number} position Index in children array where child should
  1214. * be inserted
  1215. * @return {AbbreviationNode}
  1216. */
  1217. addChild: function(child, position) {
  1218. child = child || new AbbreviationNode;
  1219. child.parent = this;
  1220. if(_.isUndefined(position)) {
  1221. this.children.push(child);
  1222. } else {
  1223. this.children.splice(position, 0, child);
  1224. }
  1225. return child;
  1226. },
  1227. /**
  1228. * Creates a deep copy of current node
  1229. * @returns {AbbreviationNode}
  1230. */
  1231. clone: function() {
  1232. var node = new AbbreviationNode();
  1233. var attrs = ['abbreviation', 'counter', '_name', '_text', 'repeatCount', 'hasImplicitRepeat', 'start', 'end', 'content', 'padding'];
  1234. _.each(attrs, function(a) {
  1235. node[a] = this[a];
  1236. }, this);
  1237. // clone attributes
  1238. node._attributes = _.map(this._attributes, function(attr) {
  1239. return _.clone(attr);
  1240. });
  1241. node._data = _.clone(this._data);
  1242. // clone children
  1243. node.children = _.map(this.children, function(child) {
  1244. child = child.clone();
  1245. child.parent = node;
  1246. return child;
  1247. });
  1248. return node;
  1249. },
  1250. /**
  1251. * Removes current node from parent‘s child list
  1252. * @returns {AbbreviationNode} Current node itself
  1253. */
  1254. remove: function() {
  1255. if(this.parent) {
  1256. this.parent.children = _.without(this.parent.children, this);
  1257. }
  1258. return this;
  1259. },
  1260. /**
  1261. * Replaces current node in parent‘s children list with passed nodes
  1262. * @param {AbbreviationNode} node Replacement node or array of nodes
  1263. */
  1264. replace: function() {
  1265. var parent = this.parent;
  1266. var ix = _.indexOf(parent.children, this);
  1267. var items = _.flatten(arguments);
  1268. spliceFn.apply(parent.children, [ix, 1].concat(items));
  1269. // update parent
  1270. _.each(items, function(item) {
  1271. item.parent = parent;
  1272. });
  1273. },
  1274. /**
  1275. * Recursively sets <code>property</code> to <code>value</code> of current
  1276. * node and its children
  1277. * @param {String} name Property to update
  1278. * @param {Object} value New property value
  1279. */
  1280. updateProperty: function(name, value) {
  1281. this[name] = value;
  1282. _.each(this.children, function(child) {
  1283. child.updateProperty(name, value);
  1284. });
  1285. },
  1286. /**
  1287. * Finds first child node that matches truth test for passed
  1288. * <code>fn</code> function
  1289. * @param {Function} fn
  1290. * @returns {AbbreviationNode}
  1291. */
  1292. find: function(fn) {
  1293. return this.findAll(fn)[0];
  1294. // if (!_.isFunction(fn)) {
  1295. // var elemName = fn.toLowerCase();
  1296. // fn = function(item) {return item.name().toLowerCase() == elemName;};
  1297. // }
  1298. //
  1299. // var result = null;
  1300. // _.find(this.children, function(child) {
  1301. // if (fn(child)) {
  1302. // return result = child;
  1303. // }
  1304. //
  1305. // return result = child.find(fn);
  1306. // });
  1307. //
  1308. // return result;
  1309. },
  1310. /**
  1311. * Finds all child nodes that matches truth test for passed
  1312. * <code>fn</code> function
  1313. * @param {Function} fn
  1314. * @returns {Array}
  1315. */
  1316. findAll: function(fn) {
  1317. if(!_.isFunction(fn)) {
  1318. var elemName = fn.toLowerCase();
  1319. fn = function(item) {
  1320. return item.name().toLowerCase() == elemName;
  1321. };
  1322. }
  1323. var result = [];
  1324. _.each(this.children, function(child) {
  1325. if(fn(child)) result.push(child);
  1326. result = result.concat(child.findAll(fn));
  1327. });
  1328. return _.compact(result);
  1329. },
  1330. /**
  1331. * Sets/gets custom data
  1332. * @param {String} name
  1333. * @param {Object} value
  1334. * @returns {Object}
  1335. */
  1336. data: function(name, value) {
  1337. if(arguments.length == 2) {
  1338. this._data[name] = value;
  1339. if(name == 'resource' && require('elements').is(value, 'snippet')) {
  1340. // setting snippet as matched resource: update `content`
  1341. // property with snippet value
  1342. this.content = value.data;
  1343. if(this._text) {
  1344. this.content = require('abbreviationUtils').insertChildContent(value.data, this._text);
  1345. }
  1346. }
  1347. }
  1348. return this._data[name];
  1349. },
  1350. /**
  1351. * Returns name of current node
  1352. * @returns {String}
  1353. */
  1354. name: function() {
  1355. var res = this.matchedResource();
  1356. if(require('elements').is(res, 'element')) {
  1357. return res.name;
  1358. }
  1359. return this._name;
  1360. },
  1361. /**
  1362. * Returns list of attributes for current node
  1363. * @returns {Array}
  1364. */
  1365. attributeList: function() {
  1366. var attrs = [];
  1367. var res = this.matchedResource();
  1368. if(require('elements').is(res, 'element') && _.isArray(res.attributes)) {
  1369. attrs = attrs.concat(res.attributes);
  1370. }
  1371. return optimizeAttributes(attrs.concat(this._attributes));
  1372. },
  1373. /**
  1374. * Returns or sets attribute value
  1375. * @param {String} name Attribute name
  1376. * @param {String} value New attribute value
  1377. * @returns {String}
  1378. */
  1379. attribute: function(name, value) {
  1380. if(arguments.length == 2) {
  1381. // modifying attribute
  1382. var ix = _.indexOf(_.pluck(this._attributes, 'name'), name.toLowerCase());
  1383. if(~ix) {
  1384. this._attributes[ix].value = value;
  1385. } else {
  1386. this._attributes.push({
  1387. name: name,
  1388. value: value
  1389. });
  1390. }
  1391. }
  1392. return(_.find(this.attributeList(), function(attr) {
  1393. return attr.name == name;
  1394. }) || {}).value;
  1395. },
  1396. /**
  1397. * Returns reference to the matched <code>element</code>, if any.
  1398. * See {@link elements} module for a list of available elements
  1399. * @returns {Object}
  1400. */
  1401. matchedResource: function() {
  1402. return this.data('resource');
  1403. },
  1404. /**
  1405. * Returns index of current node in parent‘s children list
  1406. * @returns {Number}
  1407. */
  1408. index: function() {
  1409. return this.parent ? _.indexOf(this.parent.children, this) : -1;
  1410. },
  1411. /**
  1412. * Sets how many times current element should be repeated
  1413. * @private
  1414. */
  1415. _setRepeat: function(count) {
  1416. if(count) {
  1417. this.repeatCount = parseInt(count, 10) || 1;
  1418. } else {
  1419. this.hasImplicitRepeat = true;
  1420. }
  1421. },
  1422. /**
  1423. * Sets abbreviation that belongs to current node
  1424. * @param {String} abbr
  1425. */
  1426. setAbbreviation: function(abbr) {
  1427. abbr = abbr || '';
  1428. var that = this;
  1429. // find multiplier
  1430. abbr = abbr.replace(/\*(\d+)?$/, function(str, repeatCount) {
  1431. that._setRepeat(repeatCount);
  1432. return '';
  1433. });
  1434. this.abbreviation = abbr;
  1435. var abbrText = extractText(abbr);
  1436. if(abbrText) {
  1437. abbr = abbrText.element;
  1438. this.content = this._text = abbrText.text;
  1439. }
  1440. var abbrAttrs = parseAttributes(abbr);
  1441. if(abbrAttrs) {
  1442. abbr = abbrAttrs.element;
  1443. this._attributes = abbrAttrs.attributes;
  1444. }
  1445. this._name = abbr;
  1446. // validate name
  1447. if(this._name && !reValidName.test(this._name)) {
  1448. throw 'Invalid abbreviation';
  1449. }
  1450. },
  1451. /**
  1452. * Returns string representation of current node
  1453. * @return {String}
  1454. */
  1455. toString: function() {
  1456. var utils = require('utils');
  1457. var start = this.start;
  1458. var end = this.end;
  1459. var content = this.content;
  1460. // apply output processors
  1461. var node = this;
  1462. _.each(outputProcessors, function(fn) {
  1463. start = fn(start, node, 'start');
  1464. content = fn(content, node, 'content');
  1465. end = fn(end, node, 'end');
  1466. });
  1467. var innerContent = _.map(this.children, function(child) {
  1468. return child.toString();
  1469. }).join('');
  1470. content = require('abbreviationUtils').insertChildContent(content, innerContent, {
  1471. keepVariable: false
  1472. });
  1473. return start + utils.padString(content, this.padding) + end;
  1474. },
  1475. /**
  1476. * Check if current node contains children with empty <code>expr</code>
  1477. * property
  1478. * @return {Boolean}
  1479. */
  1480. hasEmptyChildren: function() {
  1481. return !!_.find(this.children, function(child) {
  1482. return child.isEmpty();
  1483. });
  1484. },
  1485. /**
  1486. * Check if current node has implied name that should be resolved
  1487. * @returns {Boolean}
  1488. */
  1489. hasImplicitName: function() {
  1490. return !this._name && !this.isTextNode();
  1491. },
  1492. /**
  1493. * Indicates that current element is a grouping one, e.g. has no
  1494. * representation but serves as a container for other nodes
  1495. * @returns {Boolean}
  1496. */
  1497. isGroup: function() {
  1498. return !this.abbreviation;
  1499. },
  1500. /**
  1501. * Indicates empty node (i.e. without abbreviation). It may be a
  1502. * grouping node and should not be outputted
  1503. * @return {Boolean}
  1504. */
  1505. isEmpty: function() {
  1506. return !this.abbreviation && !this.children.length;
  1507. },
  1508. /**
  1509. * Indicates that current node should be repeated
  1510. * @returns {Boolean}
  1511. */
  1512. isRepeating: function() {
  1513. return this.repeatCount > 1 || this.hasImplicitRepeat;
  1514. },
  1515. /**
  1516. * Check if current node is a text-only node
  1517. * @return {Boolean}
  1518. */
  1519. isTextNode: function() {
  1520. return !this.name() && !this.attributeList().length;
  1521. },
  1522. /**
  1523. * Indicates whether this node may be used to build elements or snippets
  1524. * @returns {Boolean}
  1525. */
  1526. isElement: function() {
  1527. return !this.isEmpty() && !this.isTextNode();
  1528. },
  1529. /**
  1530. * Returns latest and deepest child of current tree
  1531. * @returns {AbbreviationNode}
  1532. */
  1533. deepestChild: function() {
  1534. if(!this.children.length) return null;
  1535. var deepestChild = this;
  1536. while(deepestChild.children.length) {
  1537. deepestChild = _.last(deepestChild.children);
  1538. }
  1539. return deepestChild;
  1540. }
  1541. };
  1542. /**
  1543. * Returns stripped string: a string without first and last character.
  1544. * Used for “unquoting” strings
  1545. * @param {String} str
  1546. * @returns {String}
  1547. */
  1548. function stripped(str) {
  1549. return str.substring(1, str.length - 1);
  1550. }
  1551. function consumeQuotedValue(stream, quote) {
  1552. var ch;
  1553. while(ch = stream.next()) {
  1554. if(ch === quote) return true;
  1555. if(ch == '\\') continue;
  1556. }
  1557. return false;
  1558. }
  1559. /**
  1560. * Parses abbreviation into a tree
  1561. * @param {String} abbr
  1562. * @returns {AbbreviationNode}
  1563. */
  1564. function parseAbbreviation(abbr) {
  1565. abbr = require('utils').trim(abbr);
  1566. var root = new AbbreviationNode;
  1567. var context = root.addChild(),
  1568. ch;
  1569. /** @type StringStream */
  1570. var stream = require('stringStream').create(abbr);
  1571. var loopProtector = 1000,
  1572. multiplier;
  1573. while(!stream.eol() && --loopProtector > 0) {
  1574. ch = stream.peek();
  1575. switch(ch) {
  1576. case '(':
  1577. // abbreviation group
  1578. stream.start = stream.pos;
  1579. if(stream.skipToPair('(', ')')) {
  1580. var inner = parseAbbreviation(stripped(stream.current()));
  1581. if(multiplier = stream.match(/^\*(\d+)?/, true)) {
  1582. context._setRepeat(multiplier[1]);
  1583. }
  1584. _.each(inner.children, function(child) {
  1585. context.addChild(child);
  1586. });
  1587. } else {
  1588. throw 'Invalid abbreviation: mo matching ")" found for character at ' + stream.pos;
  1589. }
  1590. break;
  1591. case '>':
  1592. // child operator
  1593. context = context.addChild();
  1594. stream.next();
  1595. break;
  1596. case '+':
  1597. // sibling operator
  1598. context = context.parent.addChild();
  1599. stream.next();
  1600. break;
  1601. case '^':
  1602. // climb up operator
  1603. var parent = context.parent || context;
  1604. context = (parent.parent || parent).addChild();
  1605. stream.next();
  1606. break;
  1607. default:
  1608. // consume abbreviation
  1609. stream.start = stream.pos;
  1610. stream.eatWhile(function(c) {
  1611. if(c == '[' || c == '{') {
  1612. if(stream.skipToPair(c, pairs[c])) {
  1613. stream.backUp(1);
  1614. return true;
  1615. }
  1616. throw 'Invalid abbreviation: mo matching "' + pairs[c] + '" found for character at ' + stream.pos;
  1617. }
  1618. if(c == '+') {
  1619. // let's see if this is an expando marker
  1620. stream.next();
  1621. var isMarker = stream.eol() || ~'+>^*'.indexOf(stream.peek());
  1622. stream.backUp(1);
  1623. return isMarker;
  1624. }
  1625. return c != '(' && isAllowedChar(c);
  1626. });
  1627. context.setAbbreviation(stream.current());
  1628. stream.start = stream.pos;
  1629. }
  1630. }
  1631. if(loopProtector < 1) throw 'Endless loop detected';
  1632. return root;
  1633. }
  1634. /**
  1635. * Extract attributes and their values from attribute set:
  1636. * <code>[attr col=3 title="Quoted string"]</code>
  1637. * @param {String} attrSet
  1638. * @returns {Array}
  1639. */
  1640. function extractAttributes(attrSet, attrs) {
  1641. attrSet = require('utils').trim(attrSet);
  1642. var result = [];
  1643. /** @type StringStream */
  1644. var stream = require('stringStream').create(attrSet);
  1645. stream.eatSpace();
  1646. while(!stream.eol()) {
  1647. stream.start = stream.pos;
  1648. if(stream.eatWhile(reWord)) {
  1649. var attrName = stream.current();
  1650. var attrValue = '';
  1651. if(stream.peek() == '=') {
  1652. stream.next();
  1653. stream.start = stream.pos;
  1654. var quote = stream.peek();
  1655. if(quote == '"' || quote == "'") {
  1656. stream.next();
  1657. if(consumeQuotedValue(stream, quote)) {
  1658. attrValue = stream.current();
  1659. // strip quotes
  1660. attrValue = attrValue.substring(1, attrValue.length - 1);
  1661. } else {
  1662. throw 'Invalid attribute value';
  1663. }
  1664. } else if(stream.eatWhile(/[^\s\]]/)) {
  1665. attrValue = stream.current();
  1666. } else {
  1667. throw 'Invalid attribute value';
  1668. }
  1669. }
  1670. result.push({
  1671. name: attrName,
  1672. value: attrValue
  1673. });
  1674. stream.eatSpace();
  1675. } else {
  1676. break;
  1677. }
  1678. }
  1679. return result;
  1680. }
  1681. /**
  1682. * Parses tag attributes extracted from abbreviation. If attributes found,
  1683. * returns object with <code>element</code> and <code>attributes</code>
  1684. * properties
  1685. * @param {String} abbr
  1686. * @returns {Object} Returns <code>null</code> if no attributes found in
  1687. * abbreviation
  1688. */
  1689. function parseAttributes(abbr) {
  1690. /*
  1691. * Example of incoming data:
  1692. * #header
  1693. * .some.data
  1694. * .some.data#header
  1695. * [attr]
  1696. * #item[attr=Hello other="World"].class
  1697. */
  1698. var result = [];
  1699. var attrMap = {
  1700. '#': 'id',
  1701. '.': 'class'
  1702. };
  1703. var nameEnd = null;
  1704. /** @type StringStream */
  1705. var stream = require('stringStream').create(abbr);
  1706. while(!stream.eol()) {
  1707. switch(stream.peek()) {
  1708. case '#':
  1709. // id
  1710. case '.':
  1711. // class
  1712. if(nameEnd === null) nameEnd = stream.pos;
  1713. var attrName = attrMap[stream.peek()];
  1714. stream.next();
  1715. stream.start = stream.pos;
  1716. stream.eatWhile(reWord);
  1717. result.push({
  1718. name: attrName,
  1719. value: stream.current()
  1720. });
  1721. break;
  1722. case '[':
  1723. //begin attribute set
  1724. if(nameEnd === null) nameEnd = stream.pos;
  1725. stream.start = stream.pos;
  1726. if(!stream.skipToPair('[', ']')) throw 'Invalid attribute set definition';
  1727. result = result.concat(
  1728. extractAttributes(stripped(stream.current())));
  1729. break;
  1730. default:
  1731. stream.next();
  1732. }
  1733. }
  1734. if(!result.length) return null;
  1735. return {
  1736. element: abbr.substring(0, nameEnd),
  1737. attributes: optimizeAttributes(result)
  1738. };
  1739. }
  1740. /**
  1741. * Optimize attribute set: remove duplicates and merge class attributes
  1742. * @param attrs
  1743. */
  1744. function optimizeAttributes(attrs) {
  1745. // clone all attributes to make sure that original objects are
  1746. // not modified
  1747. attrs = _.map(attrs, function(attr) {
  1748. return _.clone(attr);
  1749. });
  1750. var lookup = {};
  1751. return _.filter(attrs, function(attr) {
  1752. if(!(attr.name in lookup)) {
  1753. return lookup[attr.name] = attr;
  1754. }
  1755. var la = lookup[attr.name];
  1756. if(attr.name.toLowerCase() == 'class') {
  1757. la.value += (la.value.length ? ' ' : '') + attr.value;
  1758. } else {
  1759. la.value = attr.value;
  1760. }
  1761. return false;
  1762. });
  1763. }
  1764. /**
  1765. * Extract text data from abbreviation: if <code>a{hello}</code> abbreviation
  1766. * is passed, returns object <code>{element: 'a', text: 'hello'}</code>.
  1767. * If nothing found, returns <code>null</code>
  1768. * @param {String} abbr
  1769. *
  1770. */
  1771. function extractText(abbr) {
  1772. if(!~abbr.indexOf('{')) return null;
  1773. /** @type StringStream */
  1774. var stream = require('stringStream').create(abbr);
  1775. while(!stream.eol()) {
  1776. switch(stream.peek()) {
  1777. case '[':
  1778. case '(':
  1779. stream.skipToPair(stream.peek(), pairs[stream.peek()]);
  1780. break;
  1781. case '{':
  1782. stream.start = stream.pos;
  1783. stream.skipToPair('{', '}');
  1784. return {
  1785. element: abbr.substring(0, stream.start),
  1786. text: stripped(stream.current())
  1787. };
  1788. default:
  1789. stream.next();
  1790. }
  1791. }
  1792. }
  1793. /**
  1794. * “Un-rolls“ contents of current node: recursively replaces all repeating
  1795. * children with their repeated clones
  1796. * @param {AbbreviationNode} node
  1797. * @returns {AbbreviationNode}
  1798. */
  1799. function unroll(node) {
  1800. for(var i = node.children.length - 1, j, child; i >= 0; i--) {
  1801. child = node.children[i];
  1802. if(child.isRepeating()) {
  1803. j = child.repeatCount;
  1804. child.repeatCount = 1;
  1805. child.updateProperty('counter', 1);
  1806. while(--j > 0) {
  1807. child.parent.addChild(child.clone(), i + 1).updateProperty('counter', j + 1);
  1808. }
  1809. }
  1810. }
  1811. // to keep proper 'counter' property, we need to walk
  1812. // on children once again
  1813. _.each(node.children, unroll);
  1814. return node;
  1815. }
  1816. /**
  1817. * Optimizes tree node: replaces empty nodes with their children
  1818. * @param {AbbreviationNode} node
  1819. * @return {AbbreviationNode}
  1820. */
  1821. function squash(node) {
  1822. for(var i = node.children.length - 1; i >= 0; i--) { /** @type AbbreviationNode */
  1823. var n = node.children[i];
  1824. if(n.isGroup()) {
  1825. n.replace(squash(n).children);
  1826. } else if(n.isEmpty()) {
  1827. n.remove();
  1828. }
  1829. }
  1830. _.each(node.children, squash);
  1831. return node;
  1832. }
  1833. function isAllowedChar(ch) {
  1834. var charCode = ch.charCodeAt(0);
  1835. var specialChars = '#.*:$-_!@|';
  1836. return(charCode > 64 && charCode < 91) // uppercase letter
  1837. ||
  1838. (charCode > 96 && charCode < 123) // lowercase letter
  1839. ||
  1840. (charCode > 47 && charCode < 58) // number
  1841. ||
  1842. specialChars.indexOf(ch) != -1; // special character
  1843. }
  1844. // XXX add counter replacer function as output processor
  1845. outputProcessors.push(function(text, node) {
  1846. return require('utils').replaceCounter(text, node.counter);
  1847. });
  1848. return {
  1849. /**
  1850. * Parses abbreviation into tree with respect of groups,
  1851. * text nodes and attributes. Each node of the tree is a single
  1852. * abbreviation. Tree represents actual structure of the outputted
  1853. * result
  1854. * @memberOf abbreviationParser
  1855. * @param {String} abbr Abbreviation to parse
  1856. * @param {Object} options Additional options for parser and processors
  1857. *
  1858. * @return {AbbreviationNode}
  1859. */
  1860. parse: function(abbr, options) {
  1861. options = options || {};
  1862. var tree = parseAbbreviation(abbr);
  1863. if(options.contextNode) {
  1864. // add info about context node –
  1865. // a parent XHTML node in editor inside which abbreviation is
  1866. // expanded
  1867. tree._name = options.contextNode.name;
  1868. var attrLookup = {};
  1869. _.each(tree._attributes, function(attr) {
  1870. attrLookup[attr.name] = attr;
  1871. });
  1872. _.each(options.contextNode.attributes, function(attr) {
  1873. if(attr.name in attrLookup) {
  1874. attrLookup[attr.name].value = attr.value;
  1875. } else {
  1876. attr = _.clone(attr);
  1877. tree._attributes.push(attr);
  1878. attrLookup[attr.name] = attr;
  1879. }
  1880. });
  1881. }
  1882. // apply preprocessors
  1883. _.each(preprocessors, function(fn) {
  1884. fn(tree, options);
  1885. });
  1886. tree = squash(unroll(tree));
  1887. // apply postprocessors
  1888. _.each(postprocessors, function(fn) {
  1889. fn(tree, options);
  1890. });
  1891. return tree;
  1892. },
  1893. AbbreviationNode: AbbreviationNode,
  1894. /**
  1895. * Add new abbreviation preprocessor. <i>Preprocessor</i> is a function
  1896. * that applies to a parsed abbreviation tree right after it get parsed.
  1897. * The passed tree is in unoptimized state.
  1898. * @param {Function} fn Preprocessor function. This function receives
  1899. * two arguments: parsed abbreviation tree (<code>AbbreviationNode</code>)
  1900. * and <code>options</code> hash that was passed to <code>parse</code>
  1901. * method
  1902. */
  1903. addPreprocessor: function(fn) {
  1904. if(!_.include(preprocessors, fn)) preprocessors.push(fn);
  1905. },
  1906. /**
  1907. * Removes registered preprocessor
  1908. */
  1909. removeFilter: function(fn) {
  1910. preprocessor = _.without(preprocessors, fn);
  1911. },
  1912. /**
  1913. * Adds new abbreviation postprocessor. <i>Postprocessor</i> is a
  1914. * functinon that applies to <i>optimized</i> parsed abbreviation tree
  1915. * right before it returns from <code>parse()</code> method
  1916. * @param {Function} fn Postprocessor function. This function receives
  1917. * two arguments: parsed abbreviation tree (<code>AbbreviationNode</code>)
  1918. * and <code>options</code> hash that was passed to <code>parse</code>
  1919. * method
  1920. */
  1921. addPostprocessor: function(fn) {
  1922. if(!_.include(postprocessors, fn)) postprocessors.push(fn);
  1923. },
  1924. /**
  1925. * Removes registered postprocessor function
  1926. */
  1927. removePostprocessor: function(fn) {
  1928. postprocessors = _.without(postprocessors, fn);
  1929. },
  1930. /**
  1931. * Registers output postprocessor. <i>Output processor</i> is a
  1932. * function that applies to output part (<code>start</code>,
  1933. * <code>end</code> and <code>content</code>) when
  1934. * <code>AbbreviationNode.toString()</code> method is called
  1935. */
  1936. addOutputProcessor: function(fn) {
  1937. if(!_.include(outputProcessors, fn)) outputProcessors.push(fn);
  1938. },
  1939. /**
  1940. * Removes registered output processor
  1941. */
  1942. removeOutputProcessor: function(fn) {
  1943. outputProcessors = _.without(outputProcessors, fn);
  1944. },
  1945. /**
  1946. * Check if passed symbol is valid symbol for abbreviation expression
  1947. * @param {String} ch
  1948. * @return {Boolean}
  1949. */
  1950. isAllowedChar: function(ch) {
  1951. ch = String(ch); // convert Java object to JS
  1952. return isAllowedChar(ch) || ~'>+^[](){}'.indexOf(ch);
  1953. }
  1954. };
  1955. });
  1956. /**
  1957. * Processor function that matches parsed <code>AbbreviationNode</code>
  1958. * against resources defined in <code>resource</code> module
  1959. * @param {Function} require
  1960. * @param {Underscore} _
  1961. */
  1962. emmet.exec(function(require, _) {
  1963. /**
  1964. * Finds matched resources for child nodes of passed <code>node</code>
  1965. * element. A matched resource is a reference to <i>snippets.json</i> entry
  1966. * that describes output of parsed node
  1967. * @param {AbbreviationNode} node
  1968. * @param {String} syntax
  1969. */
  1970. function matchResources(node, syntax) {
  1971. var resources = require('resources');
  1972. var elements = require('elements');
  1973. var parser = require('abbreviationParser');
  1974. // do a shallow copy because the children list can be modified during
  1975. // resource matching
  1976. _.each(_.clone(node.children), /** @param {AbbreviationNode} child */
  1977. function(child) {
  1978. var r = resources.getMatchedResource(child, syntax);
  1979. if(_.isString(r)) {
  1980. child.data('resource', elements.create('snippet', r));
  1981. } else if(elements.is(r, 'reference')) {
  1982. // it’s a reference to another abbreviation:
  1983. // parse it and insert instead of current child
  1984. /** @type AbbreviationNode */
  1985. var subtree = parser.parse(r.data, {
  1986. syntax: syntax
  1987. });
  1988. // if context element should be repeated, check if we need to
  1989. // transfer repeated element to specific child node
  1990. if(child.repeatCount > 1) {
  1991. var repeatedChildren = subtree.findAll(function(node) {
  1992. return node.hasImplicitRepeat;
  1993. });
  1994. _.each(repeatedChildren, function(node) {
  1995. node.repeatCount = child.repeatCount;
  1996. node.hasImplicitRepeat = false;
  1997. });
  1998. }
  1999. // move child‘s children into the deepest child of new subtree
  2000. var deepestChild = subtree.deepestChild();
  2001. if(deepestChild) {
  2002. _.each(child.children, function(c) {
  2003. deepestChild.addChild(c);
  2004. });
  2005. }
  2006. // copy current attributes to children
  2007. _.each(subtree.children, function(node) {
  2008. _.each(child.attributeList(), function(attr) {
  2009. node.attribute(attr.name, attr.value);
  2010. });
  2011. });
  2012. child.replace(subtree.children);
  2013. } else {
  2014. child.data('resource', r);
  2015. }
  2016. matchResources(child, syntax);
  2017. });
  2018. }
  2019. // XXX register abbreviation filter that creates references to resources
  2020. // on abbreviation nodes
  2021. /**
  2022. * @param {AbbreviationNode} tree
  2023. */
  2024. require('abbreviationParser').addPreprocessor(function(tree, options) {
  2025. var syntax = options.syntax || emmet.defaultSyntax();
  2026. matchResources(tree, syntax);
  2027. });
  2028. });
  2029. /**
  2030. * Pasted content abbreviation processor. A pasted content is a content that
  2031. * should be inserted into implicitly repeated abbreviation nodes.
  2032. * This processor powers “Wrap With Abbreviation” action
  2033. * @param {Function} require
  2034. * @param {Underscore} _
  2035. */
  2036. emmet.exec(function(require, _) {
  2037. var parser = require('abbreviationParser');
  2038. var outputPlaceholder = '$#';
  2039. /**
  2040. * Locates output placeholders inside text
  2041. * @param {String} text
  2042. * @returns {Array} Array of ranges of output placeholder in text
  2043. */
  2044. function locateOutputPlaceholder(text) {
  2045. var range = require('range');
  2046. var result = [];
  2047. /** @type StringStream */
  2048. var stream = require('stringStream').create(text);
  2049. while(!stream.eol()) {
  2050. if(stream.peek() == '\\') {
  2051. stream.next();
  2052. } else {
  2053. stream.start = stream.pos;
  2054. if(stream.match(outputPlaceholder, true)) {
  2055. result.push(range.create(stream.start, outputPlaceholder));
  2056. continue;
  2057. }
  2058. }
  2059. stream.next();
  2060. }
  2061. return result;
  2062. }
  2063. /**
  2064. * Replaces output placeholders inside <code>source</code> with
  2065. * <code>value</code>
  2066. * @param {String} source
  2067. * @param {String} value
  2068. * @returns {String}
  2069. */
  2070. function replaceOutputPlaceholders(source, value) {
  2071. var utils = require('utils');
  2072. var ranges = locateOutputPlaceholder(source);
  2073. ranges.reverse();
  2074. _.each(ranges, function(r) {
  2075. source = utils.replaceSubstring(source, value, r);
  2076. });
  2077. return source;
  2078. }
  2079. /**
  2080. * Check if parsed node contains output placeholder – a target where
  2081. * pasted content should be inserted
  2082. * @param {AbbreviationNode} node
  2083. * @returns {Boolean}
  2084. */
  2085. function hasOutputPlaceholder(node) {
  2086. if(locateOutputPlaceholder(node.content).length) return true;
  2087. // check if attributes contains placeholder
  2088. return !!_.find(node.attributeList(), function(attr) {
  2089. return !!locateOutputPlaceholder(attr.value).length;
  2090. });
  2091. }
  2092. /**
  2093. * Insert pasted content into correct positions of parsed node
  2094. * @param {AbbreviationNode} node
  2095. * @param {String} content
  2096. * @param {Boolean} overwrite Overwrite node content if no value placeholders
  2097. * found instead of appending to existing content
  2098. */
  2099. function insertPastedContent(node, content, overwrite) {
  2100. var nodesWithPlaceholders = node.findAll(function(item) {
  2101. return hasOutputPlaceholder(item);
  2102. });
  2103. if(hasOutputPlaceholder(node)) nodesWithPlaceholders.unshift(node);
  2104. if(nodesWithPlaceholders.length) {
  2105. _.each(nodesWithPlaceholders, function(item) {
  2106. item.content = replaceOutputPlaceholders(item.content, content);
  2107. _.each(item._attributes, function(attr) {
  2108. attr.value = replaceOutputPlaceholders(attr.value, content);
  2109. });
  2110. });
  2111. } else {
  2112. // on output placeholders in subtree, insert content in the deepest
  2113. // child node
  2114. var deepest = node.deepestChild() || node;
  2115. if(overwrite) {
  2116. deepest.content = content;
  2117. } else {
  2118. deepest.content = require('abbreviationUtils').insertChildContent(deepest.content, content);
  2119. }
  2120. }
  2121. }
  2122. /**
  2123. * @param {AbbreviationNode} tree
  2124. * @param {Object} options
  2125. */
  2126. parser.addPreprocessor(function(tree, options) {
  2127. if(options.pastedContent) {
  2128. var utils = require('utils');
  2129. var lines = _.map(utils.splitByLines(options.pastedContent, true), utils.trim);
  2130. // set repeat count for implicitly repeated elements before
  2131. // tree is unrolled
  2132. tree.findAll(function(item) {
  2133. if(item.hasImplicitRepeat) {
  2134. item.data('paste', lines);
  2135. return item.repeatCount = lines.length;
  2136. }
  2137. });
  2138. }
  2139. });
  2140. /**
  2141. * @param {AbbreviationNode} tree
  2142. * @param {Object} options
  2143. */
  2144. parser.addPostprocessor(function(tree, options) {
  2145. // for each node with pasted content, update text data
  2146. var targets = tree.findAll(function(item) {
  2147. var pastedContentObj = item.data('paste');
  2148. var pastedContent = '';
  2149. if(_.isArray(pastedContentObj)) {
  2150. pastedContent = pastedContentObj[item.counter - 1];
  2151. } else if(_.isFunction(pastedContentObj)) {
  2152. pastedContent = pastedContentObj(item.counter - 1, item.content);
  2153. } else if(pastedContentObj) {
  2154. pastedContent = pastedContentObj;
  2155. }
  2156. if(pastedContent) {
  2157. insertPastedContent(item, pastedContent, !! item.data('pasteOverwrites'));
  2158. }
  2159. item.data('paste', null);
  2160. return !_.isUndefined(pastedContentObj);
  2161. });
  2162. if(!targets.length && options.pastedContent) {
  2163. // no implicitly repeated elements, put pasted content in
  2164. // the deepest child
  2165. insertPastedContent(tree, options.pastedContent);
  2166. }
  2167. });
  2168. });
  2169. /**
  2170. * Resolves tag names in abbreviations with implied name
  2171. */
  2172. emmet.exec(function(require, _) {
  2173. /**
  2174. * Resolves implicit node names in parsed tree
  2175. * @param {AbbreviationNode} tree
  2176. */
  2177. function resolveNodeNames(tree) {
  2178. var tagName = require('tagName');
  2179. _.each(tree.children, function(node) {
  2180. if(node.hasImplicitName() || node.data('forceNameResolving')) {
  2181. node._name = tagName.resolve(node.parent.name());
  2182. }
  2183. resolveNodeNames(node);
  2184. });
  2185. return tree;
  2186. }
  2187. require('abbreviationParser').addPostprocessor(resolveNodeNames);
  2188. });
  2189. /**
  2190. * @author Stoyan Stefanov
  2191. * @link https://github.com/stoyan/etc/tree/master/cssex
  2192. */
  2193. emmet.define('cssParser', function(require, _) {
  2194. var walker, tokens = [],
  2195. isOp, isNameChar, isDigit;
  2196. // walks around the source
  2197. walker = {
  2198. lines: null,
  2199. total_lines: 0,
  2200. linenum: -1,
  2201. line: '',
  2202. ch: '',
  2203. chnum: -1,
  2204. init: function(source) {
  2205. var me = walker;
  2206. // source, yumm
  2207. me.lines = source.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
  2208. me.total_lines = me.lines.length;
  2209. // reset
  2210. me.chnum = -1;
  2211. me.linenum = -1;
  2212. me.ch = '';
  2213. me.line = '';
  2214. // advance
  2215. me.nextLine();
  2216. me.nextChar();
  2217. },
  2218. nextLine: function() {
  2219. var me = this;
  2220. me.linenum += 1;
  2221. if(me.total_lines <= me.linenum) {
  2222. me.line = false;
  2223. } else {
  2224. me.line = me.lines[me.linenum];
  2225. }
  2226. if(me.chnum !== -1) {
  2227. me.chnum = 0;
  2228. }
  2229. return me.line;
  2230. },
  2231. nextChar: function() {
  2232. var me = this;
  2233. me.chnum += 1;
  2234. while(me.line.charAt(me.chnum) === '') {
  2235. if(this.nextLine() === false) {
  2236. me.ch = false;
  2237. return false; // end of source
  2238. }
  2239. me.chnum = -1;
  2240. me.ch = '\n';
  2241. return '\n';
  2242. }
  2243. me.ch = me.line.charAt(me.chnum);
  2244. return me.ch;
  2245. },
  2246. peek: function() {
  2247. return this.line.charAt(this.chnum + 1);
  2248. }
  2249. };
  2250. // utility helpers
  2251. isNameChar = function(c) {
  2252. // be more tolerate for name tokens: allow & character for LESS syntax
  2253. return(c == '&' || c === '_' || c === '-' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
  2254. };
  2255. isDigit = function(ch) {
  2256. return(ch !== false && ch >= '0' && ch <= '9');
  2257. };
  2258. isOp = (function() {
  2259. var opsa = "{}[]()+*=.,;:>~|\\%$#@^!".split(''),
  2260. opsmatcha = "*^|$~".split(''),
  2261. ops = {},
  2262. opsmatch = {},
  2263. i = 0;
  2264. for(; i < opsa.length; i += 1) {
  2265. ops[opsa[i]] = true;
  2266. }
  2267. for(i = 0; i < opsmatcha.length; i += 1) {
  2268. opsmatch[opsmatcha[i]] = true;
  2269. }
  2270. return function(ch, matchattr) {
  2271. if(matchattr) {
  2272. return !!opsmatch[ch];
  2273. }
  2274. return !!ops[ch];
  2275. };
  2276. }());
  2277. // shorthands
  2278. function isset(v) {
  2279. return typeof v !== 'undefined';
  2280. }
  2281. function getConf() {
  2282. return {
  2283. 'char': walker.chnum,
  2284. line: walker.linenum
  2285. };
  2286. }
  2287. // creates token objects and pushes them to a list
  2288. function tokener(value, type, conf) {
  2289. var w = walker,
  2290. c = conf || {};
  2291. tokens.push({
  2292. charstart: isset(c['char']) ? c['char'] : w.chnum,
  2293. charend: isset(c.charend) ? c.charend : w.chnum,
  2294. linestart: isset(c.line) ? c.line : w.linenum,
  2295. lineend: isset(c.lineend) ? c.lineend : w.linenum,
  2296. value: value,
  2297. type: type || value
  2298. });
  2299. }
  2300. // oops
  2301. function error(m, config) {
  2302. var w = walker,
  2303. conf = config || {},
  2304. c = isset(conf['char']) ? conf['char'] : w.chnum,
  2305. l = isset(conf.line) ? conf.line : w.linenum;
  2306. return {
  2307. name: "ParseError",
  2308. message: m + " at line " + (l + 1) + ' char ' + (c + 1),
  2309. walker: w,
  2310. tokens: tokens
  2311. };
  2312. }
  2313. // token handlers follow for:
  2314. // white space, comment, string, identifier, number, operator
  2315. function white() {
  2316. var c = walker.ch,
  2317. token = '',
  2318. conf = getConf();
  2319. while(c === " " || c === "\t") {
  2320. token += c;
  2321. c = walker.nextChar();
  2322. }
  2323. tokener(token, 'white', conf);
  2324. }
  2325. function comment() {
  2326. var w = walker,
  2327. c = w.ch,
  2328. token = c,
  2329. cnext, conf = getConf();
  2330. cnext = w.nextChar();
  2331. if(cnext !== '*') {
  2332. // oops, not a comment, just a /
  2333. conf.charend = conf['char'];
  2334. conf.lineend = conf.line;
  2335. return tokener(token, token, conf);
  2336. }
  2337. while(!(c === "*" && cnext === "/")) {
  2338. token += cnext;
  2339. c = cnext;
  2340. cnext = w.nextChar();
  2341. }
  2342. token += cnext;
  2343. w.nextChar();
  2344. tokener(token, 'comment', conf);
  2345. }
  2346. function str() {
  2347. var w = walker,
  2348. c = w.ch,
  2349. q = c,
  2350. token = c,
  2351. cnext, conf = getConf();
  2352. c = w.nextChar();
  2353. while(c !== q) {
  2354. if(c === '\n') {
  2355. cnext = w.nextChar();
  2356. if(cnext === "\\") {
  2357. token += c + cnext;
  2358. } else {
  2359. // end of line with no \ escape = bad
  2360. throw error("Unterminated string", conf);
  2361. }
  2362. } else {
  2363. if(c === "\\") {
  2364. token += c + w.nextChar();
  2365. } else {
  2366. token += c;
  2367. }
  2368. }
  2369. c = w.nextChar();
  2370. }
  2371. token += c;
  2372. w.nextChar();
  2373. tokener(token, 'string', conf);
  2374. }
  2375. function brace() {
  2376. var w = walker,
  2377. c = w.ch,
  2378. depth = 0,
  2379. token = c,
  2380. conf = getConf();
  2381. c = w.nextChar();
  2382. while(c !== ')' && !depth) {
  2383. if(c === '(') {
  2384. depth++;
  2385. } else if(c === ')') {
  2386. depth--;
  2387. } else if(c === false) {
  2388. throw error("Unterminated brace", conf);
  2389. }
  2390. token += c;
  2391. c = w.nextChar();
  2392. }
  2393. token += c;
  2394. w.nextChar();
  2395. tokener(token, 'brace', conf);
  2396. }
  2397. function identifier(pre) {
  2398. var w = walker,
  2399. c = w.ch,
  2400. conf = getConf(),
  2401. token = (pre) ? pre + c : c;
  2402. c = w.nextChar();
  2403. if(pre) { // adjust token position
  2404. conf['char'] -= pre.length;
  2405. }
  2406. while(isNameChar(c) || isDigit(c)) {
  2407. token += c;
  2408. c = w.nextChar();
  2409. }
  2410. tokener(token, 'identifier', conf);
  2411. }
  2412. function num() {
  2413. var w = walker,
  2414. c = w.ch,
  2415. conf = getConf(),
  2416. token = c,
  2417. point = token === '.',
  2418. nondigit;
  2419. c = w.nextChar();
  2420. nondigit = !isDigit(c);
  2421. // .2px or .classname?
  2422. if(point && nondigit) {
  2423. // meh, NaN, could be a class name, so it's an operator for now
  2424. conf.charend = conf['char'];
  2425. conf.lineend = conf.line;
  2426. return tokener(token, '.', conf);
  2427. }
  2428. // -2px or -moz-something
  2429. if(token === '-' && nondigit) {
  2430. return identifier('-');
  2431. }
  2432. while(c !== false && (isDigit(c) || (!point && c === '.'))) { // not end of source && digit or first instance of .
  2433. if(c === '.') {
  2434. point = true;
  2435. }
  2436. token += c;
  2437. c = w.nextChar();
  2438. }
  2439. tokener(token, 'number', conf);
  2440. }
  2441. function op() {
  2442. var w = walker,
  2443. c = w.ch,
  2444. conf = getConf(),
  2445. token = c,
  2446. next = w.nextChar();
  2447. if(next === "=" && isOp(token, true)) {
  2448. token += next;
  2449. tokener(token, 'match', conf);
  2450. w.nextChar();
  2451. return;
  2452. }
  2453. conf.charend = conf['char'] + 1;
  2454. conf.lineend = conf.line;
  2455. tokener(token, token, conf);
  2456. }
  2457. // call the appropriate handler based on the first character in a token suspect
  2458. function tokenize() {
  2459. var ch = walker.ch;
  2460. if(ch === " " || ch === "\t") {
  2461. return white();
  2462. }
  2463. if(ch === '/') {
  2464. return comment();
  2465. }
  2466. if(ch === '"' || ch === "'") {
  2467. return str();
  2468. }
  2469. if(ch === '(') {
  2470. return brace();
  2471. }
  2472. if(ch === '-' || ch === '.' || isDigit(ch)) { // tricky - char: minus (-1px) or dash (-moz-stuff)
  2473. return num();
  2474. }
  2475. if(isNameChar(ch)) {
  2476. return identifier();
  2477. }
  2478. if(isOp(ch)) {
  2479. return op();
  2480. }
  2481. if(ch === "\n") {
  2482. tokener("line");
  2483. walker.nextChar();
  2484. return;
  2485. }
  2486. throw error("Unrecognized character");
  2487. }
  2488. /**
  2489. * Returns newline character at specified position in content
  2490. * @param {String} content
  2491. * @param {Number} pos
  2492. * @return {String}
  2493. */
  2494. function getNewline(content, pos) {
  2495. return content.charAt(pos) == '\r' && content.charAt(pos + 1) == '\n' ? '\r\n' : content.charAt(pos);
  2496. }
  2497. return {
  2498. /**
  2499. * @param source
  2500. * @returns
  2501. * @memberOf emmet.cssParser
  2502. */
  2503. lex: function(source) {
  2504. walker.init(source);
  2505. tokens = [];
  2506. while(walker.ch !== false) {
  2507. tokenize();
  2508. }
  2509. return tokens;
  2510. },
  2511. /**
  2512. * Tokenizes CSS source
  2513. * @param {String} source
  2514. * @returns {Array}
  2515. */
  2516. parse: function(source) {
  2517. // transform tokens
  2518. var pos = 0;
  2519. return _.map(this.lex(source), function(token) {
  2520. if(token.type == 'line') {
  2521. token.value = getNewline(source, pos);
  2522. }
  2523. return {
  2524. type: token.type,
  2525. start: pos,
  2526. end: (pos += token.value.length)
  2527. };
  2528. });
  2529. },
  2530. toSource: function(toks) {
  2531. var i = 0,
  2532. max = toks.length,
  2533. t, src = '';
  2534. for(; i < max; i += 1) {
  2535. t = toks[i];
  2536. if(t.type === 'line') {
  2537. src += '\n';
  2538. } else {
  2539. src += t.value;
  2540. }
  2541. }
  2542. return src;
  2543. }
  2544. };
  2545. });
  2546. /**
  2547. * HTML tokenizer by Marijn Haverbeke
  2548. * http://codemirror.net/
  2549. * @constructor
  2550. * @memberOf __xmlParseDefine
  2551. * @param {Function} require
  2552. * @param {Underscore} _
  2553. */
  2554. emmet.define('xmlParser', function(require, _) {
  2555. var Kludges = {
  2556. autoSelfClosers: {},
  2557. implicitlyClosed: {},
  2558. contextGrabbers: {},
  2559. doNotIndent: {},
  2560. allowUnquoted: true,
  2561. allowMissing: true
  2562. };
  2563. // Return variables for tokenizers
  2564. var tagName = null,
  2565. type = null;
  2566. function inText(stream, state) {
  2567. function chain(parser) {
  2568. state.tokenize = parser;
  2569. return parser(stream, state);
  2570. }
  2571. var ch = stream.next();
  2572. if(ch == "<") {
  2573. if(stream.eat("!")) {
  2574. if(stream.eat("[")) {
  2575. if(stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
  2576. else return null;
  2577. } else if(stream.match("--")) return chain(inBlock("comment", "-->"));
  2578. else if(stream.match("DOCTYPE", true, true)) {
  2579. stream.eatWhile(/[\w\._\-]/);
  2580. return chain(doctype(1));
  2581. } else return null;
  2582. } else if(stream.eat("?")) {
  2583. stream.eatWhile(/[\w\._\-]/);
  2584. state.tokenize = inBlock("meta", "?>");
  2585. return "meta";
  2586. } else {
  2587. type = stream.eat("/") ? "closeTag" : "openTag";
  2588. stream.eatSpace();
  2589. tagName = "";
  2590. var c;
  2591. while((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/)))
  2592. tagName += c;
  2593. state.tokenize = inTag;
  2594. return "tag";
  2595. }
  2596. } else if(ch == "&") {
  2597. var ok;
  2598. if(stream.eat("#")) {
  2599. if(stream.eat("x")) {
  2600. ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
  2601. } else {
  2602. ok = stream.eatWhile(/[\d]/) && stream.eat(";");
  2603. }
  2604. } else {
  2605. ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
  2606. }
  2607. return ok ? "atom" : "error";
  2608. } else {
  2609. stream.eatWhile(/[^&<]/);
  2610. return "text";
  2611. }
  2612. }
  2613. function inTag(stream, state) {
  2614. var ch = stream.next();
  2615. if(ch == ">" || (ch == "/" && stream.eat(">"))) {
  2616. state.tokenize = inText;
  2617. type = ch == ">" ? "endTag" : "selfcloseTag";
  2618. return "tag";
  2619. } else if(ch == "=") {
  2620. type = "equals";
  2621. return null;
  2622. } else if(/[\'\"]/.test(ch)) {
  2623. state.tokenize = inAttribute(ch);
  2624. return state.tokenize(stream, state);
  2625. } else {
  2626. stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
  2627. return "word";
  2628. }
  2629. }
  2630. function inAttribute(quote) {
  2631. return function(stream, state) {
  2632. while(!stream.eol()) {
  2633. if(stream.next() == quote) {
  2634. state.tokenize = inTag;
  2635. break;
  2636. }
  2637. }
  2638. return "string";
  2639. };
  2640. }
  2641. function inBlock(style, terminator) {
  2642. return function(stream, state) {
  2643. while(!stream.eol()) {
  2644. if(stream.match(terminator)) {
  2645. state.tokenize = inText;
  2646. break;
  2647. }
  2648. stream.next();
  2649. }
  2650. return style;
  2651. };
  2652. }
  2653. function doctype(depth) {
  2654. return function(stream, state) {
  2655. var ch;
  2656. while((ch = stream.next()) != null) {
  2657. if(ch == "<") {
  2658. state.tokenize = doctype(depth + 1);
  2659. return state.tokenize(stream, state);
  2660. } else if(ch == ">") {
  2661. if(depth == 1) {
  2662. state.tokenize = inText;
  2663. break;
  2664. } else {
  2665. state.tokenize = doctype(depth - 1);
  2666. return state.tokenize(stream, state);
  2667. }
  2668. }
  2669. }
  2670. return "meta";
  2671. };
  2672. }
  2673. var curState = null,
  2674. setStyle;
  2675. function pass() {
  2676. for(var i = arguments.length - 1; i >= 0; i--)
  2677. curState.cc.push(arguments[i]);
  2678. }
  2679. function cont() {
  2680. pass.apply(null, arguments);
  2681. return true;
  2682. }
  2683. function pushContext(tagName, startOfLine) {
  2684. var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
  2685. curState.context = {
  2686. prev: curState.context,
  2687. tagName: tagName,
  2688. indent: curState.indented,
  2689. startOfLine: startOfLine,
  2690. noIndent: noIndent
  2691. };
  2692. }
  2693. function popContext() {
  2694. if(curState.context) curState.context = curState.context.prev;
  2695. }
  2696. function element(type) {
  2697. if(type == "openTag") {
  2698. curState.tagName = tagName;
  2699. return cont(attributes, endtag(curState.startOfLine));
  2700. } else if(type == "closeTag") {
  2701. var err = false;
  2702. if(curState.context) {
  2703. if(curState.context.tagName != tagName) {
  2704. if(Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
  2705. popContext();
  2706. }
  2707. err = !curState.context || curState.context.tagName != tagName;
  2708. }
  2709. } else {
  2710. err = true;
  2711. }
  2712. if(err) setStyle = "error";
  2713. return cont(endclosetag(err));
  2714. }
  2715. return cont();
  2716. }
  2717. function endtag(startOfLine) {
  2718. return function(type) {
  2719. if(type == "selfcloseTag" || (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
  2720. maybePopContext(curState.tagName.toLowerCase());
  2721. return cont();
  2722. }
  2723. if(type == "endTag") {
  2724. maybePopContext(curState.tagName.toLowerCase());
  2725. pushContext(curState.tagName, startOfLine);
  2726. return cont();
  2727. }
  2728. return cont();
  2729. };
  2730. }
  2731. function endclosetag(err) {
  2732. return function(type) {
  2733. if(err) setStyle = "error";
  2734. if(type == "endTag") {
  2735. popContext();
  2736. return cont();
  2737. }
  2738. setStyle = "error";
  2739. return cont(arguments.callee);
  2740. };
  2741. }
  2742. function maybePopContext(nextTagName) {
  2743. var parentTagName;
  2744. while(true) {
  2745. if(!curState.context) {
  2746. return;
  2747. }
  2748. parentTagName = curState.context.tagName.toLowerCase();
  2749. if(!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
  2750. return;
  2751. }
  2752. popContext();
  2753. }
  2754. }
  2755. function attributes(type) {
  2756. if(type == "word") {
  2757. setStyle = "attribute";
  2758. return cont(attribute, attributes);
  2759. }
  2760. if(type == "endTag" || type == "selfcloseTag") return pass();
  2761. setStyle = "error";
  2762. return cont(attributes);
  2763. }
  2764. function attribute(type) {
  2765. if(type == "equals") return cont(attvalue, attributes);
  2766. if(!Kludges.allowMissing) setStyle = "error";
  2767. return(type == "endTag" || type == "selfcloseTag") ? pass() : cont();
  2768. }
  2769. function attvalue(type) {
  2770. if(type == "string") return cont(attvaluemaybe);
  2771. if(type == "word" && Kludges.allowUnquoted) {
  2772. setStyle = "string";
  2773. return cont();
  2774. }
  2775. setStyle = "error";
  2776. return(type == "endTag" || type == "selfCloseTag") ? pass() : cont();
  2777. }
  2778. function attvaluemaybe(type) {
  2779. if(type == "string") return cont(attvaluemaybe);
  2780. else return pass();
  2781. }
  2782. function startState() {
  2783. return {
  2784. tokenize: inText,
  2785. cc: [],
  2786. indented: 0,
  2787. startOfLine: true,
  2788. tagName: null,
  2789. context: null
  2790. };
  2791. }
  2792. function token(stream, state) {
  2793. if(stream.sol()) {
  2794. state.startOfLine = true;
  2795. state.indented = 0;
  2796. }
  2797. if(stream.eatSpace()) return null;
  2798. setStyle = type = tagName = null;
  2799. var style = state.tokenize(stream, state);
  2800. state.type = type;
  2801. if((style || type) && style != "comment") {
  2802. curState = state;
  2803. while(true) {
  2804. var comb = state.cc.pop() || element;
  2805. if(comb(type || style)) break;
  2806. }
  2807. }
  2808. state.startOfLine = false;
  2809. return setStyle || style;
  2810. }
  2811. return {
  2812. /**
  2813. * @memberOf emmet.xmlParser
  2814. * @returns
  2815. */
  2816. parse: function(data, offset) {
  2817. offset = offset || 0;
  2818. var state = startState();
  2819. var stream = require('stringStream').create(data);
  2820. var tokens = [];
  2821. while(!stream.eol()) {
  2822. tokens.push({
  2823. type: token(stream, state),
  2824. start: stream.start + offset,
  2825. end: stream.pos + offset
  2826. });
  2827. stream.start = stream.pos;
  2828. }
  2829. return tokens;
  2830. }
  2831. };
  2832. });
  2833. /**
  2834. * Utility module for Emmet
  2835. * @param {Function} require
  2836. * @param {Underscore} _
  2837. */
  2838. emmet.define('utils', function(require, _) {
  2839. /**
  2840. * Special token used as a placeholder for caret positions inside
  2841. * generated output
  2842. */
  2843. var caretPlaceholder = '${0}';
  2844. /**
  2845. * A simple string builder, optimized for faster text concatenation
  2846. * @param {String} value Initial value
  2847. */
  2848. function StringBuilder(value) {
  2849. this._data = [];
  2850. this.length = 0;
  2851. if(value) this.append(value);
  2852. }
  2853. StringBuilder.prototype = {
  2854. /**
  2855. * Append string
  2856. * @param {String} text
  2857. */
  2858. append: function(text) {
  2859. this._data.push(text);
  2860. this.length += text.length;
  2861. },
  2862. /**
  2863. * @returns {String}
  2864. */
  2865. toString: function() {
  2866. return this._data.join('');
  2867. },
  2868. /**
  2869. * @returns {String}
  2870. */
  2871. valueOf: function() {
  2872. return this.toString();
  2873. }
  2874. };
  2875. return { /** @memberOf utils */
  2876. reTag: /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/,
  2877. /**
  2878. * Test if passed string ends with XHTML tag. This method is used for testing
  2879. * '>' character: it belongs to tag or it's a part of abbreviation?
  2880. * @param {String} str
  2881. * @return {Boolean}
  2882. */
  2883. endsWithTag: function(str) {
  2884. return this.reTag.test(str);
  2885. },
  2886. /**
  2887. * Check if passed symbol is a number
  2888. * @param {String} ch
  2889. * @returns {Boolean}
  2890. */
  2891. isNumeric: function(ch) {
  2892. if(typeof(ch) == 'string') ch = ch.charCodeAt(0);
  2893. return(ch && ch > 47 && ch < 58);
  2894. },
  2895. /**
  2896. * Trim whitespace from string
  2897. * @param {String} text
  2898. * @return {String}
  2899. */
  2900. trim: function(text) {
  2901. return(text || "").replace(/^\s+|\s+$/g, "");
  2902. },
  2903. /**
  2904. * Returns newline character
  2905. * @returns {String}
  2906. */
  2907. getNewline: function() {
  2908. var res = require('resources');
  2909. if(!res) {
  2910. return '\n';
  2911. }
  2912. var nl = res.getVariable('newline');
  2913. return _.isString(nl) ? nl : '\n';
  2914. },
  2915. /**
  2916. * Sets new newline character that will be used in output
  2917. * @param {String} str
  2918. */
  2919. setNewline: function(str) {
  2920. var res = require('resources');
  2921. res.setVariable('newline', str);
  2922. res.setVariable('nl', str);
  2923. },
  2924. /**
  2925. * Split text into lines. Set <code>remove_empty</code> to true to filter
  2926. * empty lines
  2927. * @param {String} text Text to split
  2928. * @param {Boolean} removeEmpty Remove empty lines from result
  2929. * @return {Array}
  2930. */
  2931. splitByLines: function(text, removeEmpty) {
  2932. // IE fails to split string by regexp,
  2933. // need to normalize newlines first
  2934. // Also, Mozilla's Rhiho JS engine has a weird newline bug
  2935. var nl = this.getNewline();
  2936. var lines = (text || '').replace(/\r\n/g, '\n').replace(/\n\r/g, '\n').replace(/\r/g, '\n').replace(/\n/g, nl).split(nl);
  2937. if(removeEmpty) {
  2938. lines = _.filter(lines, function(line) {
  2939. return line.length && !! this.trim(line);
  2940. }, this);
  2941. }
  2942. return lines;
  2943. },
  2944. /**
  2945. * Normalizes newline character: replaces newlines in <code>text</code>
  2946. * with newline defined in preferences
  2947. * @param {String} text
  2948. * @returns {String}
  2949. */
  2950. normalizeNewline: function(text) {
  2951. return this.splitByLines(text).join(this.getNewline());
  2952. },
  2953. /**
  2954. * Repeats string <code>howMany</code> times
  2955. * @param {String} str
  2956. * @param {Number} how_many
  2957. * @return {String}
  2958. */
  2959. repeatString: function(str, howMany) {
  2960. var result = [];
  2961. for(var i = 0; i < howMany; i++)
  2962. result.push(str);
  2963. return result.join('');
  2964. },
  2965. /**
  2966. * Indents text with padding
  2967. * @param {String} text Text to indent
  2968. * @param {String} pad Padding size (number) or padding itself (string)
  2969. * @return {String}
  2970. */
  2971. padString: function(text, pad) {
  2972. var padStr = (_.isNumber(pad)) ? this.repeatString(require('resources').getVariable('indentation') || '\t', pad) : pad;
  2973. var result = [];
  2974. var lines = this.splitByLines(text);
  2975. var nl = this.getNewline();
  2976. result.push(lines[0]);
  2977. for(var j = 1; j < lines.length; j++)
  2978. result.push(nl + padStr + lines[j]);
  2979. return result.join('');
  2980. },
  2981. /**
  2982. * Pad string with zeroes
  2983. * @param {String} str String to pad
  2984. * @param {Number} pad Desired string length
  2985. * @return {String}
  2986. */
  2987. zeroPadString: function(str, pad) {
  2988. var padding = '';
  2989. var il = str.length;
  2990. while(pad > il++) padding += '0';
  2991. return padding + str;
  2992. },
  2993. /**
  2994. * Removes padding at the beginning of each text's line
  2995. * @param {String} text
  2996. * @param {String} pad
  2997. */
  2998. unindentString: function(text, pad) {
  2999. var lines = this.splitByLines(text);
  3000. for(var i = 0; i < lines.length; i++) {
  3001. if(lines[i].search(pad) == 0) lines[i] = lines[i].substr(pad.length);
  3002. }
  3003. return lines.join(this.getNewline());
  3004. },
  3005. /**
  3006. * Replaces unescaped symbols in <code>str</code>. For example, the '$' symbol
  3007. * will be replaced in 'item$count', but not in 'item\$count'.
  3008. * @param {String} str Original string
  3009. * @param {String} symbol Symbol to replace
  3010. * @param {String} replace Symbol replacement. Might be a function that
  3011. * returns new value
  3012. * @return {String}
  3013. */
  3014. replaceUnescapedSymbol: function(str, symbol, replace) {
  3015. var i = 0;
  3016. var il = str.length;
  3017. var sl = symbol.length;
  3018. var matchCount = 0;
  3019. while(i < il) {
  3020. if(str.charAt(i) == '\\') {
  3021. // escaped symbol, skip next character
  3022. i += sl + 1;
  3023. } else if(str.substr(i, sl) == symbol) {
  3024. // have match
  3025. var curSl = sl;
  3026. matchCount++;
  3027. var newValue = replace;
  3028. if(_.isFunction(replace)) {
  3029. var replaceData = replace(str, symbol, i, matchCount);
  3030. if(replaceData) {
  3031. curSl = replaceData[0].length;
  3032. newValue = replaceData[1];
  3033. } else {
  3034. newValue = false;
  3035. }
  3036. }
  3037. if(newValue === false) { // skip replacement
  3038. i++;
  3039. continue;
  3040. }
  3041. str = str.substring(0, i) + newValue + str.substring(i + curSl);
  3042. // adjust indexes
  3043. il = str.length;
  3044. i += newValue.length;
  3045. } else {
  3046. i++;
  3047. }
  3048. }
  3049. return str;
  3050. },
  3051. /**
  3052. * Replace variables like ${var} in string
  3053. * @param {String} str
  3054. * @param {Object} vars Variable set (defaults to variables defined in
  3055. * <code>snippets.json</code>) or variable resolver (<code>Function</code>)
  3056. * @return {String}
  3057. */
  3058. replaceVariables: function(str, vars) {
  3059. vars = vars || {};
  3060. var resolver = _.isFunction(vars) ? vars : function(str, p1) {
  3061. return p1 in vars ? vars[p1] : null;
  3062. };
  3063. var res = require('resources');
  3064. return require('tabStops').processText(str, {
  3065. variable: function(data) {
  3066. var newValue = resolver(data.token, data.name, data);
  3067. if(newValue === null) {
  3068. // try to find variable in resources
  3069. newValue = res.getVariable(data.name);
  3070. }
  3071. if(newValue === null || _.isUndefined(newValue))
  3072. // nothing found, return token itself
  3073. newValue = data.token;
  3074. return newValue;
  3075. }
  3076. });
  3077. },
  3078. /**
  3079. * Replaces '$' character in string assuming it might be escaped with '\'
  3080. * @param {String} str String where caracter should be replaced
  3081. * @param {String} value Replace value. Might be a <code>Function</code>
  3082. * @return {String}
  3083. */
  3084. replaceCounter: function(str, value) {
  3085. var symbol = '$';
  3086. // in case we received strings from Java, convert the to native strings
  3087. str = String(str);
  3088. value = String(value);
  3089. var that = this;
  3090. return this.replaceUnescapedSymbol(str, symbol, function(str, symbol, pos, matchNum) {
  3091. if(str.charAt(pos + 1) == '{' || that.isNumeric(str.charAt(pos + 1))) {
  3092. // it's a variable, skip it
  3093. return false;
  3094. }
  3095. // replace sequense of $ symbols with padded number
  3096. var j = pos + 1;
  3097. while(str.charAt(j) == '$' && str.charAt(j + 1) != '{') j++;
  3098. return [str.substring(pos, j), that.zeroPadString(value, j - pos)];
  3099. });
  3100. },
  3101. /**
  3102. * Check if string matches against <code>reTag</code> regexp. This
  3103. * function may be used to test if provided string contains HTML tags
  3104. * @param {String} str
  3105. * @returns {Boolean}
  3106. */
  3107. matchesTag: function(str) {
  3108. return this.reTag.test(str || '');
  3109. },
  3110. /**
  3111. * Escapes special characters used in Emmet, like '$', '|', etc.
  3112. * Use this method before passing to actions like "Wrap with Abbreviation"
  3113. * to make sure that existing special characters won't be altered
  3114. * @param {String} text
  3115. * @return {String}
  3116. */
  3117. escapeText: function(text) {
  3118. return text.replace(/([\$\\])/g, '\\$1');
  3119. },
  3120. /**
  3121. * Unescapes special characters used in Emmet, like '$', '|', etc.
  3122. * @param {String} text
  3123. * @return {String}
  3124. */
  3125. unescapeText: function(text) {
  3126. return text.replace(/\\(.)/g, '$1');
  3127. },
  3128. /**
  3129. * Returns caret placeholder
  3130. * @returns {String}
  3131. */
  3132. getCaretPlaceholder: function() {
  3133. return _.isFunction(caretPlaceholder) ? caretPlaceholder.apply(this, arguments) : caretPlaceholder;
  3134. },
  3135. /**
  3136. * Sets new representation for carets in generated output
  3137. * @param {String} value New caret placeholder. Might be a
  3138. * <code>Function</code>
  3139. */
  3140. setCaretPlaceholder: function(value) {
  3141. caretPlaceholder = value;
  3142. },
  3143. /**
  3144. * Returns line padding
  3145. * @param {String} line
  3146. * @return {String}
  3147. */
  3148. getLinePadding: function(line) {
  3149. return(line.match(/^(\s+)/) || [''])[0];
  3150. },
  3151. /**
  3152. * Helper function that returns padding of line of <code>pos</code>
  3153. * position in <code>content</code>
  3154. * @param {String} content
  3155. * @param {Number} pos
  3156. * @returns {String}
  3157. */
  3158. getLinePaddingFromPosition: function(content, pos) {
  3159. var lineRange = this.findNewlineBounds(content, pos);
  3160. return this.getLinePadding(lineRange.substring(content));
  3161. },
  3162. /**
  3163. * Escape special regexp chars in string, making it usable for creating dynamic
  3164. * regular expressions
  3165. * @param {String} str
  3166. * @return {String}
  3167. */
  3168. escapeForRegexp: function(str) {
  3169. var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\
  3170. return str.replace(specials, "\\$&");
  3171. },
  3172. /**
  3173. * Make decimal number look good: convert it to fixed precision end remove
  3174. * traling zeroes
  3175. * @param {Number} num
  3176. * @param {Number} fracion Fraction numbers (default is 2)
  3177. * @return {String}
  3178. */
  3179. prettifyNumber: function(num, fraction) {
  3180. return num.toFixed(typeof fraction == 'undefined' ? 2 : fraction).replace(/\.?0+$/, '');
  3181. },
  3182. /**
  3183. * A simple mutable string shim, optimized for faster text concatenation
  3184. * @param {String} value Initial value
  3185. * @returns {StringBuilder}
  3186. */
  3187. stringBuilder: function(value) {
  3188. return new StringBuilder(value);
  3189. },
  3190. /**
  3191. * Replace substring of <code>str</code> with <code>value</code>
  3192. * @param {String} str String where to replace substring
  3193. * @param {String} value New substring value
  3194. * @param {Number} start Start index of substring to replace. May also
  3195. * be a <code>Range</code> object: in this case, the <code>end</code>
  3196. * argument is not required
  3197. * @param {Number} end End index of substring to replace. If ommited,
  3198. * <code>start</code> argument is used
  3199. */
  3200. replaceSubstring: function(str, value, start, end) {
  3201. if(_.isObject(start) && 'end' in start) {
  3202. end = start.end;
  3203. start = start.start;
  3204. }
  3205. if(_.isString(end)) end = start + end.length;
  3206. if(_.isUndefined(end)) end = start;
  3207. if(start < 0 || start > str.length) return str;
  3208. return str.substring(0, start) + value + str.substring(end);
  3209. },
  3210. /**
  3211. * Narrows down text range, adjusting selection to non-space characters
  3212. * @param {String} text
  3213. * @param {Number} start Starting range in <code>text</code> where
  3214. * slection should be adjusted. Can also be any object that is accepted
  3215. * by <code>Range</code> class
  3216. * @return {Range}
  3217. */
  3218. narrowToNonSpace: function(text, start, end) {
  3219. var range = require('range').create(start, end);
  3220. var reSpace = /[\s\n\r\u00a0]/;
  3221. // narrow down selection until first non-space character
  3222. while(range.start < range.end) {
  3223. if(!reSpace.test(text.charAt(range.start))) break;
  3224. range.start++;
  3225. }
  3226. while(range.end > range.start) {
  3227. range.end--;
  3228. if(!reSpace.test(text.charAt(range.end))) {
  3229. range.end++;
  3230. break;
  3231. }
  3232. }
  3233. return range;
  3234. },
  3235. /**
  3236. * Find start and end index of text line for <code>from</code> index
  3237. * @param {String} text
  3238. * @param {Number} from
  3239. */
  3240. findNewlineBounds: function(text, from) {
  3241. var len = text.length,
  3242. start = 0,
  3243. end = len - 1;
  3244. // search left
  3245. for(var i = from - 1; i > 0; i--) {
  3246. var ch = text.charAt(i);
  3247. if(ch == '\n' || ch == '\r') {
  3248. start = i + 1;
  3249. break;
  3250. }
  3251. }
  3252. // search right
  3253. for(var j = from; j < len; j++) {
  3254. var ch = text.charAt(j);
  3255. if(ch == '\n' || ch == '\r') {
  3256. end = j;
  3257. break;
  3258. }
  3259. }
  3260. return require('range').create(start, end - start);
  3261. },
  3262. /**
  3263. * Deep merge of two or more objects. Taken from jQuery.extend()
  3264. */
  3265. deepMerge: function() {
  3266. var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
  3267. i = 1,
  3268. length = arguments.length;
  3269. // Handle case when target is a string or something (possible in deep copy)
  3270. if(!_.isObject(target) && !_.isFunction(target)) {
  3271. target = {};
  3272. }
  3273. for(; i < length; i++) {
  3274. // Only deal with non-null/undefined values
  3275. if((options = arguments[i]) != null) {
  3276. // Extend the base object
  3277. for(name in options) {
  3278. src = target[name];
  3279. copy = options[name];
  3280. // Prevent never-ending loop
  3281. if(target === copy) {
  3282. continue;
  3283. }
  3284. // Recurse if we're merging plain objects or arrays
  3285. if(copy && (_.isObject(copy) || (copyIsArray = _.isArray(copy)))) {
  3286. if(copyIsArray) {
  3287. copyIsArray = false;
  3288. clone = src && _.isArray(src) ? src : [];
  3289. } else {
  3290. clone = src && _.isObject(src) ? src : {};
  3291. }
  3292. // Never move original objects, clone them
  3293. target[name] = this.deepMerge(clone, copy);
  3294. // Don't bring in undefined values
  3295. } else if(copy !== undefined) {
  3296. target[name] = copy;
  3297. }
  3298. }
  3299. }
  3300. }
  3301. // Return the modified object
  3302. return target;
  3303. }
  3304. };
  3305. });
  3306. /**
  3307. * Helper module to work with ranges
  3308. * @constructor
  3309. * @memberOf __rangeDefine
  3310. * @param {Function} require
  3311. * @param {Underscore} _
  3312. */
  3313. emmet.define('range', function(require, _) {
  3314. /**
  3315. * @type Range
  3316. * @constructor
  3317. * @param {Object} start
  3318. * @param {Number} len
  3319. */
  3320. function Range(start, len) {
  3321. if(_.isObject(start) && 'start' in start) {
  3322. // create range from object stub
  3323. this.start = Math.min(start.start, start.end);
  3324. this.end = Math.max(start.start, start.end);
  3325. } else if(_.isArray(start)) {
  3326. this.start = start[0];
  3327. this.end = start[1];
  3328. } else {
  3329. len = _.isString(len) ? len.length : +len;
  3330. this.start = start;
  3331. this.end = start + len;
  3332. }
  3333. }
  3334. Range.prototype = {
  3335. length: function() {
  3336. return Math.abs(this.end - this.start);
  3337. },
  3338. /**
  3339. * Returns <code>true</code> if passed range is equals to current one
  3340. * @param {Range} range
  3341. * @returns {Boolean}
  3342. */
  3343. equal: function(range) {
  3344. return this.start === range.start && this.end === range.end;
  3345. },
  3346. /**
  3347. * Shifts indexes position with passed <code>delat</code>
  3348. * @param {Number} delta
  3349. * @returns {Range} range itself
  3350. */
  3351. shift: function(delta) {
  3352. this.start += delta;
  3353. this.end += delta;
  3354. return this;
  3355. },
  3356. /**
  3357. * Check if two ranges are overlapped
  3358. * @param {Range} range
  3359. * @returns {Boolean}
  3360. */
  3361. overlap: function(range) {
  3362. return range.start <= this.end && range.end >= this.start;
  3363. },
  3364. /**
  3365. * Finds intersection of two ranges
  3366. * @param {Range} range
  3367. * @returns {Range} <code>null</code> if ranges does not overlap
  3368. */
  3369. intersection: function(range) {
  3370. if(this.overlap(range)) {
  3371. var start = Math.max(range.start, this.start);
  3372. var end = Math.min(range.end, this.end);
  3373. return new Range(start, end - start);
  3374. }
  3375. return null;
  3376. },
  3377. /**
  3378. * Returns the union of the thow ranges.
  3379. * @param {Range} range
  3380. * @returns {Range} <code>null</code> if ranges are not overlapped
  3381. */
  3382. union: function(range) {
  3383. if(this.overlap(range)) {
  3384. var start = Math.min(range.start, this.start);
  3385. var end = Math.max(range.end, this.end);
  3386. return new Range(start, end - start);
  3387. }
  3388. return null;
  3389. },
  3390. /**
  3391. * Returns a Boolean value that indicates whether a specified position
  3392. * is in a given range.
  3393. * @param {Number} loc
  3394. */
  3395. inside: function(loc) {
  3396. return this.start <= loc && this.end > loc;
  3397. },
  3398. /**
  3399. * Check if current range completely includes specified one
  3400. * @param {Range} r
  3401. * @returns {Boolean}
  3402. */
  3403. include: function(r) {
  3404. return this.start <= r.start && this.end >= r.end;
  3405. },
  3406. /**
  3407. * Returns substring of specified <code>str</code> for current range
  3408. * @param {String} str
  3409. * @returns {String}
  3410. */
  3411. substring: function(str) {
  3412. return this.length() > 0 ? str.substring(this.start, this.end) : '';
  3413. },
  3414. /**
  3415. * Creates copy of current range
  3416. * @returns {Range}
  3417. */
  3418. clone: function() {
  3419. return new Range(this.start, this.length());
  3420. },
  3421. /**
  3422. * @returns {Array}
  3423. */
  3424. toArray: function() {
  3425. return [this.start, this.end];
  3426. },
  3427. toString: function() {
  3428. return '{' + this.start + ', ' + this.length() + '}';
  3429. }
  3430. };
  3431. return {
  3432. /**
  3433. * Creates new range object instance
  3434. * @param {Object} start Range start or array with 'start' and 'end'
  3435. * as two first indexes or object with 'start' and 'end' properties
  3436. * @param {Number} len Range length or string to produce range from
  3437. * @returns {Range}
  3438. * @memberOf emmet.range
  3439. */
  3440. create: function(start, len) {
  3441. if(_.isUndefined(start) || start === null) return null;
  3442. if(start instanceof Range) return start;
  3443. if(_.isObject(start) && 'start' in start && 'end' in start) {
  3444. len = start.end - start.start;
  3445. start = start.start;
  3446. }
  3447. return new Range(start, len);
  3448. },
  3449. /**
  3450. * <code>Range</code> object factory, the same as <code>this.create()</code>
  3451. * but last argument represents end of range, not length
  3452. * @returns {Range}
  3453. */
  3454. create2: function(start, end) {
  3455. if(_.isNumber(start) && _.isNumber(end)) {
  3456. end -= start;
  3457. }
  3458. return this.create(start, end);
  3459. }
  3460. };
  3461. });
  3462. /**
  3463. * Utility module that provides ordered storage of function handlers.
  3464. * Many Emmet modules' functionality can be extended/overridden by custom
  3465. * function. This modules provides unified storage of handler functions, their
  3466. * management and execution
  3467. *
  3468. * @constructor
  3469. * @memberOf __handlerListDefine
  3470. * @param {Function} require
  3471. * @param {Underscore} _
  3472. */
  3473. emmet.define('handlerList', function(require, _) {
  3474. /**
  3475. * @type HandlerList
  3476. * @constructor
  3477. */
  3478. function HandlerList() {
  3479. this._list = [];
  3480. }
  3481. HandlerList.prototype = {
  3482. /**
  3483. * Adds function handler
  3484. * @param {Function} fn Handler
  3485. * @param {Object} options Handler options. Possible values are:<br><br>
  3486. * <b>order</b> : (<code>Number</code>) – order in handler list. Handlers
  3487. * with higher order value will be executed earlier.
  3488. */
  3489. add: function(fn, options) {
  3490. this._list.push(_.extend({
  3491. order: 0
  3492. }, options || {}, {
  3493. fn: fn
  3494. }));
  3495. },
  3496. /**
  3497. * Removes handler from list
  3498. * @param {Function} fn
  3499. */
  3500. remove: function(fn) {
  3501. this._list = _.without(this._list, _.find(this._list, function(item) {
  3502. return item.fn === fn;
  3503. }));
  3504. },
  3505. /**
  3506. * Returns ordered list of handlers. By default, handlers
  3507. * with the same <code>order</code> option returned in reverse order,
  3508. * i.e. the latter function was added into the handlers list, the higher
  3509. * it will be in the returned array
  3510. * @returns {Array}
  3511. */
  3512. list: function() {
  3513. return _.sortBy(this._list, 'order').reverse();
  3514. },
  3515. /**
  3516. * Returns ordered list of handler functions
  3517. * @returns {Array}
  3518. */
  3519. listFn: function() {
  3520. return _.pluck(this.list(), 'fn');
  3521. },
  3522. /**
  3523. * Executes handler functions in their designated order. If function
  3524. * returns <code>skipVal</code>, meaning that function was unable to
  3525. * handle passed <code>args</code>, the next function will be executed
  3526. * and so on.
  3527. * @param {Object} skipValue If function returns this value, execute
  3528. * next handler.
  3529. * @param {Array} args Arguments to pass to handler function
  3530. * @returns {Boolean} Whether any of registered handlers performed
  3531. * successfully
  3532. */
  3533. exec: function(skipValue, args) {
  3534. args = args || [];
  3535. var result = null;
  3536. _.find(this.list(), function(h) {
  3537. result = h.fn.apply(h, args);
  3538. if(result !== skipValue) return true;
  3539. });
  3540. return result;
  3541. }
  3542. };
  3543. return {
  3544. /**
  3545. * Factory method that produces <code>HandlerList</code> instance
  3546. * @returns {HandlerList}
  3547. * @memberOf handlerList
  3548. */
  3549. create: function() {
  3550. return new HandlerList();
  3551. }
  3552. };
  3553. });
  3554. /**
  3555. * Helper class for convenient token iteration
  3556. */
  3557. emmet.define('tokenIterator', function(require, _) {
  3558. /**
  3559. * @type TokenIterator
  3560. * @param {Array} tokens
  3561. * @type TokenIterator
  3562. * @constructor
  3563. */
  3564. function TokenIterator(tokens) { /** @type Array */
  3565. this.tokens = tokens;
  3566. this._position = 0;
  3567. this.reset();
  3568. }
  3569. TokenIterator.prototype = {
  3570. next: function() {
  3571. if(this.hasNext()) {
  3572. var token = this.tokens[++this._i];
  3573. this._position = token.start;
  3574. return token;
  3575. }
  3576. return null;
  3577. },
  3578. current: function() {
  3579. return this.tokens[this._i];
  3580. },
  3581. position: function() {
  3582. return this._position;
  3583. },
  3584. hasNext: function() {
  3585. return this._i < this._il - 1;
  3586. },
  3587. reset: function() {
  3588. this._i = -1;
  3589. this._il = this.tokens.length;
  3590. },
  3591. item: function() {
  3592. return this.tokens[this._i];
  3593. },
  3594. itemNext: function() {
  3595. return this.tokens[this._i + 1];
  3596. },
  3597. itemPrev: function() {
  3598. return this.tokens[this._i - 1];
  3599. },
  3600. nextUntil: function(type, callback) {
  3601. var token;
  3602. var test = _.isString(type) ?
  3603. function(t) {
  3604. return t.type == type;
  3605. } : type;
  3606. while(token = this.next()) {
  3607. if(callback) callback.call(this, token);
  3608. if(test.call(this, token)) break;
  3609. }
  3610. }
  3611. };
  3612. return {
  3613. create: function(tokens) {
  3614. return new TokenIterator(tokens);
  3615. }
  3616. };
  3617. });
  3618. /**
  3619. * A trimmed version of CodeMirror's StringStream module for string parsing
  3620. */
  3621. emmet.define('stringStream', function(require, _) {
  3622. /**
  3623. * @type StringStream
  3624. * @constructor
  3625. * @param {String} string
  3626. */
  3627. function StringStream(string) {
  3628. this.pos = this.start = 0;
  3629. this.string = string;
  3630. }
  3631. StringStream.prototype = {
  3632. /**
  3633. * Returns true only if the stream is at the end of the line.
  3634. * @returns {Boolean}
  3635. */
  3636. eol: function() {
  3637. return this.pos >= this.string.length;
  3638. },
  3639. /**
  3640. * Returns true only if the stream is at the start of the line
  3641. * @returns {Boolean}
  3642. */
  3643. sol: function() {
  3644. return this.pos == 0;
  3645. },
  3646. /**
  3647. * Returns the next character in the stream without advancing it.
  3648. * Will return <code>undefined</code> at the end of the line.
  3649. * @returns {String}
  3650. */
  3651. peek: function() {
  3652. return this.string.charAt(this.pos);
  3653. },
  3654. /**
  3655. * Returns the next character in the stream and advances it.
  3656. * Also returns <code>undefined</code> when no more characters are available.
  3657. * @returns {String}
  3658. */
  3659. next: function() {
  3660. if(this.pos < this.string.length) return this.string.charAt(this.pos++);
  3661. },
  3662. /**
  3663. * match can be a character, a regular expression, or a function that
  3664. * takes a character and returns a boolean. If the next character in the
  3665. * stream 'matches' the given argument, it is consumed and returned.
  3666. * Otherwise, undefined is returned.
  3667. * @param {Object} match
  3668. * @returns {String}
  3669. */
  3670. eat: function(match) {
  3671. var ch = this.string.charAt(this.pos),
  3672. ok;
  3673. if(typeof match == "string") ok = ch == match;
  3674. else ok = ch && (match.test ? match.test(ch) : match(ch));
  3675. if(ok) {
  3676. ++this.pos;
  3677. return ch;
  3678. }
  3679. },
  3680. /**
  3681. * Repeatedly calls <code>eat</code> with the given argument, until it
  3682. * fails. Returns <code>true</code> if any characters were eaten.
  3683. * @param {Object} match
  3684. * @returns {Boolean}
  3685. */
  3686. eatWhile: function(match) {
  3687. var start = this.pos;
  3688. while(this.eat(match)) {}
  3689. return this.pos > start;
  3690. },
  3691. /**
  3692. * Shortcut for <code>eatWhile</code> when matching white-space.
  3693. * @returns {Boolean}
  3694. */
  3695. eatSpace: function() {
  3696. var start = this.pos;
  3697. while(/[\s\u00a0]/.test(this.string.charAt(this.pos)))++this.pos;
  3698. return this.pos > start;
  3699. },
  3700. /**
  3701. * Moves the position to the end of the line.
  3702. */
  3703. skipToEnd: function() {
  3704. this.pos = this.string.length;
  3705. },
  3706. /**
  3707. * Skips to the next occurrence of the given character, if found on the
  3708. * current line (doesn't advance the stream if the character does not
  3709. * occur on the line). Returns true if the character was found.
  3710. * @param {String} ch
  3711. * @returns {Boolean}
  3712. */
  3713. skipTo: function(ch) {
  3714. var found = this.string.indexOf(ch, this.pos);
  3715. if(found > -1) {
  3716. this.pos = found;
  3717. return true;
  3718. }
  3719. },
  3720. /**
  3721. * Skips to <code>close</code> character which is pair to <code>open</code>
  3722. * character, considering possible pair nesting. This function is used
  3723. * to consume pair of characters, like opening and closing braces
  3724. * @param {String} open
  3725. * @param {String} close
  3726. * @returns {Boolean} Returns <code>true</code> if pair was successfully
  3727. * consumed
  3728. */
  3729. skipToPair: function(open, close) {
  3730. var braceCount = 0,
  3731. ch;
  3732. var pos = this.pos,
  3733. len = this.string.length;
  3734. while(pos < len) {
  3735. ch = this.string.charAt(pos++);
  3736. if(ch == open) {
  3737. braceCount++;
  3738. } else if(ch == close) {
  3739. braceCount--;
  3740. if(braceCount < 1) {
  3741. this.pos = pos;
  3742. return true;
  3743. }
  3744. }
  3745. }
  3746. return false;
  3747. },
  3748. /**
  3749. * Backs up the stream n characters. Backing it up further than the
  3750. * start of the current token will cause things to break, so be careful.
  3751. * @param {Number} n
  3752. */
  3753. backUp: function(n) {
  3754. this.pos -= n;
  3755. },
  3756. /**
  3757. * Act like a multi-character <code>eat</code>—if <code>consume</code> is true or
  3758. * not given—or a look-ahead that doesn't update the stream position—if
  3759. * it is false. <code>pattern</code> can be either a string or a
  3760. * regular expression starting with ^. When it is a string,
  3761. * <code>caseInsensitive</code> can be set to true to make the match
  3762. * case-insensitive. When successfully matching a regular expression,
  3763. * the returned value will be the array returned by <code>match</code>,
  3764. * in case you need to extract matched groups.
  3765. *
  3766. * @param {RegExp} pattern
  3767. * @param {Boolean} consume
  3768. * @param {Boolean} caseInsensitive
  3769. * @returns
  3770. */
  3771. match: function(pattern, consume, caseInsensitive) {
  3772. if(typeof pattern == "string") {
  3773. var cased = caseInsensitive ?
  3774. function(str) {
  3775. return str.toLowerCase();
  3776. } : function(str) {
  3777. return str;
  3778. };
  3779. if(cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
  3780. if(consume !== false) this.pos += pattern.length;
  3781. return true;
  3782. }
  3783. } else {
  3784. var match = this.string.slice(this.pos).match(pattern);
  3785. if(match && consume !== false) this.pos += match[0].length;
  3786. return match;
  3787. }
  3788. },
  3789. /**
  3790. * Get the string between the start of the current token and the
  3791. * current stream position.
  3792. * @returns {String}
  3793. */
  3794. current: function() {
  3795. return this.string.slice(this.start, this.pos);
  3796. }
  3797. };
  3798. return {
  3799. create: function(string) {
  3800. return new StringStream(string);
  3801. }
  3802. };
  3803. });
  3804. /**
  3805. * Parsed resources (snippets, abbreviations, variables, etc.) for Emmet.
  3806. * Contains convenient method to get access for snippets with respect of
  3807. * inheritance. Also provides ability to store data in different vocabularies
  3808. * ('system' and 'user') for fast and safe resource update
  3809. * @author Sergey Chikuyonok (serge.che@gmail.com)
  3810. * @link http://chikuyonok.ru
  3811. *
  3812. * @param {Function} require
  3813. * @param {Underscore} _
  3814. */
  3815. emmet.define('resources', function(require, _) {
  3816. var VOC_SYSTEM = 'system';
  3817. var VOC_USER = 'user';
  3818. var cache = {};
  3819. /** Regular expression for XML tag matching */
  3820. var reTag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/;
  3821. var systemSettings = {};
  3822. var userSettings = {};
  3823. /** @type HandlerList List of registered abbreviation resolvers */
  3824. var resolvers = require('handlerList').create();
  3825. /**
  3826. * Normalizes caret plceholder in passed text: replaces | character with
  3827. * default caret placeholder
  3828. * @param {String} text
  3829. * @returns {String}
  3830. */
  3831. function normalizeCaretPlaceholder(text) {
  3832. var utils = require('utils');
  3833. return utils.replaceUnescapedSymbol(text, '|', utils.getCaretPlaceholder());
  3834. }
  3835. function parseItem(name, value, type) {
  3836. value = normalizeCaretPlaceholder(value);
  3837. if(type == 'snippets') {
  3838. return require('elements').create('snippet', value);
  3839. }
  3840. if(type == 'abbreviations') {
  3841. return parseAbbreviation(name, value);
  3842. }
  3843. }
  3844. /**
  3845. * Parses single abbreviation
  3846. * @param {String} key Abbreviation name
  3847. * @param {String} value Abbreviation value
  3848. * @return {Object}
  3849. */
  3850. function parseAbbreviation(key, value) {
  3851. key = require('utils').trim(key);
  3852. var elements = require('elements');
  3853. var m;
  3854. if(m = reTag.exec(value)) {
  3855. return elements.create('element', m[1], m[2], m[4] == '/');
  3856. } else {
  3857. // assume it's reference to another abbreviation
  3858. return elements.create('reference', value);
  3859. }
  3860. }
  3861. return {
  3862. /**
  3863. * Sets new unparsed data for specified settings vocabulary
  3864. * @param {Object} data
  3865. * @param {String} type Vocabulary type ('system' or 'user')
  3866. * @memberOf resources
  3867. */
  3868. setVocabulary: function(data, type) {
  3869. cache = {};
  3870. if(type == VOC_SYSTEM) systemSettings = data;
  3871. else userSettings = data;
  3872. },
  3873. /**
  3874. * Returns resource vocabulary by its name
  3875. * @param {String} name Vocabulary name ('system' or 'user')
  3876. * @return {Object}
  3877. */
  3878. getVocabulary: function(name) {
  3879. return name == VOC_SYSTEM ? systemSettings : userSettings;
  3880. },
  3881. /**
  3882. * Returns resource (abbreviation, snippet, etc.) matched for passed
  3883. * abbreviation
  3884. * @param {TreeNode} node
  3885. * @param {String} syntax
  3886. * @returns {Object}
  3887. */
  3888. getMatchedResource: function(node, syntax) {
  3889. return resolvers.exec(null, _.toArray(arguments)) || this.findSnippet(syntax, node.name());
  3890. },
  3891. /**
  3892. * Returns variable value
  3893. * @return {String}
  3894. */
  3895. getVariable: function(name) {
  3896. return(this.getSection('variables') || {})[name];
  3897. },
  3898. /**
  3899. * Store runtime variable in user storage
  3900. * @param {String} name Variable name
  3901. * @param {String} value Variable value
  3902. */
  3903. setVariable: function(name, value) {
  3904. var voc = this.getVocabulary('user') || {};
  3905. if(!('variables' in voc)) voc.variables = {};
  3906. voc.variables[name] = value;
  3907. this.setVocabulary(voc, 'user');
  3908. },
  3909. /**
  3910. * Check if there are resources for specified syntax
  3911. * @param {String} syntax
  3912. * @return {Boolean}
  3913. */
  3914. hasSyntax: function(syntax) {
  3915. return syntax in this.getVocabulary(VOC_USER) || syntax in this.getVocabulary(VOC_SYSTEM);
  3916. },
  3917. /**
  3918. * Registers new abbreviation resolver.
  3919. * @param {Function} fn Abbreviation resolver which will receive
  3920. * abbreviation as first argument and should return parsed abbreviation
  3921. * object if abbreviation has handled successfully, <code>null</code>
  3922. * otherwise
  3923. * @param {Object} options Options list as described in
  3924. * {@link HandlerList#add()} method
  3925. */
  3926. addResolver: function(fn, options) {
  3927. resolvers.add(fn, options);
  3928. },
  3929. removeResolver: function(fn) {
  3930. resolvers.remove(fn);
  3931. },
  3932. /**
  3933. * Returns actual section data, merged from both
  3934. * system and user data
  3935. * @param {String} name Section name (syntax)
  3936. * @param {String} ...args Subsections
  3937. * @returns
  3938. */
  3939. getSection: function(name) {
  3940. if(!name) return null;
  3941. if(!(name in cache)) {
  3942. cache[name] = require('utils').deepMerge({}, systemSettings[name], userSettings[name]);
  3943. }
  3944. var data = cache[name],
  3945. subsections = _.rest(arguments),
  3946. key;
  3947. while(data && (key = subsections.shift())) {
  3948. if(key in data) {
  3949. data = data[key];
  3950. } else {
  3951. return null;
  3952. }
  3953. }
  3954. return data;
  3955. },
  3956. /**
  3957. * Recursively searches for a item inside top level sections (syntaxes)
  3958. * with respect of `extends` attribute
  3959. * @param {String} topSection Top section name (syntax)
  3960. * @param {String} subsection Inner section name
  3961. * @returns {Object}
  3962. */
  3963. findItem: function(topSection, subsection) {
  3964. var data = this.getSection(topSection);
  3965. while(data) {
  3966. if(subsection in data) return data[subsection];
  3967. data = this.getSection(data['extends']);
  3968. }
  3969. },
  3970. /**
  3971. * Recursively searches for a snippet definition inside syntax section.
  3972. * Definition is searched inside `snippets` and `abbreviations`
  3973. * subsections
  3974. * @param {String} syntax Top-level section name (syntax)
  3975. * @param {Snippet name} name
  3976. * @returns {Object}
  3977. */
  3978. findSnippet: function(syntax, name, memo) {
  3979. if(!syntax || !name) return null;
  3980. memo = memo || [];
  3981. var names = [name];
  3982. // create automatic aliases to properties with colons,
  3983. // e.g. pos-a == pos:a
  3984. if(~name.indexOf('-')) names.push(name.replace(/\-/g, ':'));
  3985. var data = this.getSection(syntax),
  3986. matchedItem = null;
  3987. _.find(['snippets', 'abbreviations'], function(sectionName) {
  3988. var data = this.getSection(syntax, sectionName);
  3989. if(data) {
  3990. return _.find(names, function(n) {
  3991. if(data[n]) return matchedItem = parseItem(n, data[n], sectionName);
  3992. });
  3993. }
  3994. }, this);
  3995. memo.push(syntax);
  3996. if(!matchedItem && data['extends'] && !_.include(memo, data['extends'])) {
  3997. // try to find item in parent syntax section
  3998. return this.findSnippet(data['extends'], name, memo);
  3999. }
  4000. return matchedItem;
  4001. }
  4002. };
  4003. });
  4004. /**
  4005. * Module describes and performs Emmet actions. The actions themselves are
  4006. * defined in <i>actions</i> folder
  4007. * @param {Function} require
  4008. * @param {Underscore} _
  4009. */
  4010. emmet.define('actions', function(require, _, zc) {
  4011. var actions = {};
  4012. /**
  4013. * “Humanizes” action name, makes it more readable for people
  4014. * @param {String} name Action name (like 'expand_abbreviation')
  4015. * @return Humanized name (like 'Expand Abbreviation')
  4016. */
  4017. function humanizeActionName(name) {
  4018. return require('utils').trim(name.charAt(0).toUpperCase() + name.substring(1).replace(/_[a-z]/g, function(str) {
  4019. return ' ' + str.charAt(1).toUpperCase();
  4020. }));
  4021. }
  4022. return {
  4023. /**
  4024. * Registers new action
  4025. * @param {String} name Action name
  4026. * @param {Function} fn Action function
  4027. * @param {Object} options Custom action options:<br>
  4028. * <b>label</b> : (<code>String</code>) – Human-readable action name.
  4029. * May contain '/' symbols as submenu separators<br>
  4030. * <b>hidden</b> : (<code>Boolean</code>) – Indicates whether action
  4031. * should be displayed in menu (<code>getMenu()</code> method)
  4032. *
  4033. * @memberOf actions
  4034. */
  4035. add: function(name, fn, options) {
  4036. name = name.toLowerCase();
  4037. options = options || {};
  4038. if(!options.label) {
  4039. options.label = humanizeActionName(name);
  4040. }
  4041. actions[name] = {
  4042. name: name,
  4043. fn: fn,
  4044. options: options
  4045. };
  4046. },
  4047. /**
  4048. * Returns action object
  4049. * @param {String} name Action name
  4050. * @returns {Object}
  4051. */
  4052. get: function(name) {
  4053. return actions[name.toLowerCase()];
  4054. },
  4055. /**
  4056. * Runs Emmet action. For list of available actions and their
  4057. * arguments see <i>actions</i> folder.
  4058. * @param {String} name Action name
  4059. * @param {Array} args Additional arguments. It may be array of arguments
  4060. * or inline arguments. The first argument should be <code>IEmmetEditor</code> instance
  4061. * @returns {Boolean} Status of performed operation, <code>true</code>
  4062. * means action was performed successfully.
  4063. * @example
  4064. * emmet.require('actions').run('expand_abbreviation', editor);
  4065. * emmet.require('actions').run('wrap_with_abbreviation', [editor, 'div']);
  4066. */
  4067. run: function(name, args) {
  4068. if(!_.isArray(args)) {
  4069. args = _.rest(arguments);
  4070. }
  4071. var action = this.get(name);
  4072. if(action) {
  4073. return action.fn.apply(emmet, args);
  4074. } else {
  4075. emmet.log('Action "%s" is not defined', name);
  4076. return false;
  4077. }
  4078. },
  4079. /**
  4080. * Returns all registered actions as object
  4081. * @returns {Object}
  4082. */
  4083. getAll: function() {
  4084. return actions;
  4085. },
  4086. /**
  4087. * Returns all registered actions as array
  4088. * @returns {Array}
  4089. */
  4090. getList: function() {
  4091. return _.values(this.getAll());
  4092. },
  4093. /**
  4094. * Returns actions list as structured menu. If action has <i>label</i>,
  4095. * it will be splitted by '/' symbol into submenus (for example:
  4096. * CSS/Reflect Value) and grouped with other items
  4097. * @param {Array} skipActions List of action identifiers that should be
  4098. * skipped from menu
  4099. * @returns {Array}
  4100. */
  4101. getMenu: function(skipActions) {
  4102. var result = [];
  4103. skipActions = skipActions || [];
  4104. _.each(this.getList(), function(action) {
  4105. if(action.options.hidden || _.include(skipActions, action.name)) return;
  4106. var actionName = humanizeActionName(action.name);
  4107. var ctx = result;
  4108. if(action.options.label) {
  4109. var parts = action.options.label.split('/');
  4110. actionName = parts.pop();
  4111. // create submenus, if needed
  4112. var menuName, submenu;
  4113. while(menuName = parts.shift()) {
  4114. submenu = _.find(ctx, function(item) {
  4115. return item.type == 'submenu' && item.name == menuName;
  4116. });
  4117. if(!submenu) {
  4118. submenu = {
  4119. name: menuName,
  4120. type: 'submenu',
  4121. items: []
  4122. };
  4123. ctx.push(submenu);
  4124. }
  4125. ctx = submenu.items;
  4126. }
  4127. }
  4128. ctx.push({
  4129. type: 'action',
  4130. name: action.name,
  4131. label: actionName
  4132. });
  4133. });
  4134. return result;
  4135. },
  4136. /**
  4137. * Returns action name associated with menu item title
  4138. * @param {String} title
  4139. * @returns {String}
  4140. */
  4141. getActionNameForMenuTitle: function(title, menu) {
  4142. var item = null;
  4143. _.find(menu || this.getMenu(), function(val) {
  4144. if(val.type == 'action') {
  4145. if(val.label == title || val.name == title) {
  4146. return item = val.name;
  4147. }
  4148. } else {
  4149. return item = this.getActionNameForMenuTitle(title, val.items);
  4150. }
  4151. }, this);
  4152. return item || null;
  4153. }
  4154. };
  4155. });
  4156. /**
  4157. * Output profile module.
  4158. * Profile defines how XHTML output data should look like
  4159. * @param {Function} require
  4160. * @param {Underscore} _
  4161. */
  4162. emmet.define('profile', function(require, _) {
  4163. var profiles = {};
  4164. var defaultProfile = {
  4165. tag_case: 'asis',
  4166. attr_case: 'asis',
  4167. attr_quotes: 'double',
  4168. // each tag on new line
  4169. tag_nl: 'decide',
  4170. // with tag_nl === true, defines if leaf node (e.g. node with no children)
  4171. // should have formatted line breaks
  4172. tag_nl_leaf: false,
  4173. place_cursor: true,
  4174. // indent tags
  4175. indent: true,
  4176. // how many inline elements should be to force line break
  4177. // (set to 0 to disable)
  4178. inline_break: 3,
  4179. // use self-closing style for writing empty elements, e.g. <br /> or <br>
  4180. self_closing_tag: 'xhtml',
  4181. // Profile-level output filters, re-defines syntax filters
  4182. filters: ''
  4183. };
  4184. /**
  4185. * @constructor
  4186. * @type OutputProfile
  4187. * @param {Object} options
  4188. */
  4189. function OutputProfile(options) {
  4190. _.extend(this, defaultProfile, options);
  4191. }
  4192. OutputProfile.prototype = {
  4193. /**
  4194. * Transforms tag name case depending on current profile settings
  4195. * @param {String} name String to transform
  4196. * @returns {String}
  4197. */
  4198. tagName: function(name) {
  4199. return stringCase(name, this.tag_case);
  4200. },
  4201. /**
  4202. * Transforms attribute name case depending on current profile settings
  4203. * @param {String} name String to transform
  4204. * @returns {String}
  4205. */
  4206. attributeName: function(name) {
  4207. return stringCase(name, this.attr_case);
  4208. },
  4209. /**
  4210. * Returns quote character for current profile
  4211. * @returns {String}
  4212. */
  4213. attributeQuote: function() {
  4214. return this.attr_quotes == 'single' ? "'" : '"';
  4215. },
  4216. /**
  4217. * Returns self-closing tag symbol for current profile
  4218. * @param {String} param
  4219. * @returns {String}
  4220. */
  4221. selfClosing: function(param) {
  4222. if(this.self_closing_tag == 'xhtml') return ' /';
  4223. if(this.self_closing_tag === true) return '/';
  4224. return '';
  4225. },
  4226. /**
  4227. * Returns cursor token based on current profile settings
  4228. * @returns {String}
  4229. */
  4230. cursor: function() {
  4231. return this.place_cursor ? require('utils').getCaretPlaceholder() : '';
  4232. }
  4233. };
  4234. /**
  4235. * Helper function that converts string case depending on
  4236. * <code>caseValue</code>
  4237. * @param {String} str String to transform
  4238. * @param {String} caseValue Case value: can be <i>lower</i>,
  4239. * <i>upper</i> and <i>leave</i>
  4240. * @returns {String}
  4241. */
  4242. function stringCase(str, caseValue) {
  4243. switch(String(caseValue || '').toLowerCase()) {
  4244. case 'lower':
  4245. return str.toLowerCase();
  4246. case 'upper':
  4247. return str.toUpperCase();
  4248. }
  4249. return str;
  4250. }
  4251. /**
  4252. * Creates new output profile
  4253. * @param {String} name Profile name
  4254. * @param {Object} options Profile options
  4255. */
  4256. function createProfile(name, options) {
  4257. return profiles[name.toLowerCase()] = new OutputProfile(options);
  4258. }
  4259. function createDefaultProfiles() {
  4260. createProfile('xhtml');
  4261. createProfile('html', {
  4262. self_closing_tag: false
  4263. });
  4264. createProfile('xml', {
  4265. self_closing_tag: true,
  4266. tag_nl: true
  4267. });
  4268. createProfile('plain', {
  4269. tag_nl: false,
  4270. indent: false,
  4271. place_cursor: false
  4272. });
  4273. createProfile('line', {
  4274. tag_nl: false,
  4275. indent: false
  4276. });
  4277. }
  4278. createDefaultProfiles();
  4279. return {
  4280. /**
  4281. * Creates new output profile and adds it into internal dictionary
  4282. * @param {String} name Profile name
  4283. * @param {Object} options Profile options
  4284. * @memberOf emmet.profile
  4285. * @returns {Object} New profile
  4286. */
  4287. create: function(name, options) {
  4288. if(arguments.length == 2) return createProfile(name, options);
  4289. else
  4290. // create profile object only
  4291. return new OutputProfile(_.defaults(name || {}, defaultProfile));
  4292. },
  4293. /**
  4294. * Returns profile by its name. If profile wasn't found, returns
  4295. * 'plain' profile
  4296. * @param {String} name Profile name. Might be profile itself
  4297. * @param {String} syntax. Optional. Current editor syntax. If defined,
  4298. * profile is searched in resources first, then in predefined profiles
  4299. * @returns {Object}
  4300. */
  4301. get: function(name, syntax) {
  4302. if(syntax && _.isString(name)) {
  4303. // search in user resources first
  4304. var profile = require('resources').findItem(syntax, 'profile');
  4305. if(profile) {
  4306. name = profile;
  4307. }
  4308. }
  4309. if(!name) return profiles.plain;
  4310. if(name instanceof OutputProfile) return name;
  4311. if(_.isString(name) && name.toLowerCase() in profiles) return profiles[name.toLowerCase()];
  4312. return this.create(name);
  4313. },
  4314. /**
  4315. * Deletes profile with specified name
  4316. * @param {String} name Profile name
  4317. */
  4318. remove: function(name) {
  4319. name = (name || '').toLowerCase();
  4320. if(name in profiles) delete profiles[name];
  4321. },
  4322. /**
  4323. * Resets all user-defined profiles
  4324. */
  4325. reset: function() {
  4326. profiles = {};
  4327. createDefaultProfiles();
  4328. },
  4329. /**
  4330. * Helper function that converts string case depending on
  4331. * <code>caseValue</code>
  4332. * @param {String} str String to transform
  4333. * @param {String} caseValue Case value: can be <i>lower</i>,
  4334. * <i>upper</i> and <i>leave</i>
  4335. * @returns {String}
  4336. */
  4337. stringCase: stringCase
  4338. };
  4339. });
  4340. /**
  4341. * Utility module used to prepare text for pasting into back-end editor
  4342. * @param {Function} require
  4343. * @param {Underscore} _
  4344. * @author Sergey Chikuyonok (serge.che@gmail.com) <http://chikuyonok.ru>
  4345. */
  4346. emmet.define('editorUtils', function(require, _) {
  4347. return {
  4348. /**
  4349. * Check if cursor is placed inside XHTML tag
  4350. * @param {String} html Contents of the document
  4351. * @param {Number} caretPos Current caret position inside tag
  4352. * @return {Boolean}
  4353. */
  4354. isInsideTag: function(html, caretPos) {
  4355. var reTag = /^<\/?\w[\w\:\-]*.*?>/;
  4356. // search left to find opening brace
  4357. var pos = caretPos;
  4358. while(pos > -1) {
  4359. if(html.charAt(pos) == '<') break;
  4360. pos--;
  4361. }
  4362. if(pos != -1) {
  4363. var m = reTag.exec(html.substring(pos));
  4364. if(m && caretPos > pos && caretPos < pos + m[0].length) return true;
  4365. }
  4366. return false;
  4367. },
  4368. /**
  4369. * Sanitizes incoming editor data and provides default values for
  4370. * output-specific info
  4371. * @param {IEmmetEditor} editor
  4372. * @param {String} syntax
  4373. * @param {String} profile
  4374. */
  4375. outputInfo: function(editor, syntax, profile) {
  4376. return { /** @memberOf outputInfo */
  4377. syntax: String(syntax || editor.getSyntax()),
  4378. profile: String(profile || editor.getProfileName()),
  4379. content: String(editor.getContent())
  4380. };
  4381. },
  4382. /**
  4383. * Unindent content, thus preparing text for tag wrapping
  4384. * @param {IEmmetEditor} editor Editor instance
  4385. * @param {String} text
  4386. * @return {String}
  4387. */
  4388. unindent: function(editor, text) {
  4389. return require('utils').unindentString(text, this.getCurrentLinePadding(editor));
  4390. },
  4391. /**
  4392. * Returns padding of current editor's line
  4393. * @param {IEmmetEditor} Editor instance
  4394. * @return {String}
  4395. */
  4396. getCurrentLinePadding: function(editor) {
  4397. return require('utils').getLinePadding(editor.getCurrentLine());
  4398. }
  4399. };
  4400. });
  4401. /**
  4402. * Utility methods for Emmet actions
  4403. * @param {Function} require
  4404. * @param {Underscore} _
  4405. * @author Sergey Chikuyonok (serge.che@gmail.com) <http://chikuyonok.ru>
  4406. */
  4407. emmet.define('actionUtils', function(require, _) {
  4408. return {
  4409. mimeTypes: {
  4410. 'gif': 'image/gif',
  4411. 'png': 'image/png',
  4412. 'jpg': 'image/jpeg',
  4413. 'jpeg': 'image/jpeg',
  4414. 'svg': 'image/svg+xml',
  4415. 'html': 'text/html',
  4416. 'htm': 'text/html'
  4417. },
  4418. /**
  4419. * Extracts abbreviations from text stream, starting from the end
  4420. * @param {String} str
  4421. * @return {String} Abbreviation or empty string
  4422. * @memberOf emmet.actionUtils
  4423. */
  4424. extractAbbreviation: function(str) {
  4425. var curOffset = str.length;
  4426. var startIndex = -1;
  4427. var groupCount = 0;
  4428. var braceCount = 0;
  4429. var textCount = 0;
  4430. var utils = require('utils');
  4431. var parser = require('abbreviationParser');
  4432. while(true) {
  4433. curOffset--;
  4434. if(curOffset < 0) {
  4435. // moved to the beginning of the line
  4436. startIndex = 0;
  4437. break;
  4438. }
  4439. var ch = str.charAt(curOffset);
  4440. if(ch == ']') {
  4441. braceCount++;
  4442. } else if(ch == '[') {
  4443. if(!braceCount) { // unexpected brace
  4444. startIndex = curOffset + 1;
  4445. break;
  4446. }
  4447. braceCount--;
  4448. } else if(ch == '}') {
  4449. textCount++;
  4450. } else if(ch == '{') {
  4451. if(!textCount) { // unexpected brace
  4452. startIndex = curOffset + 1;
  4453. break;
  4454. }
  4455. textCount--;
  4456. } else if(ch == ')') {
  4457. groupCount++;
  4458. } else if(ch == '(') {
  4459. if(!groupCount) { // unexpected brace
  4460. startIndex = curOffset + 1;
  4461. break;
  4462. }
  4463. groupCount--;
  4464. } else {
  4465. if(braceCount || textCount)
  4466. // respect all characters inside attribute sets or text nodes
  4467. continue;
  4468. else if(!parser.isAllowedChar(ch) || (ch == '>' && utils.endsWithTag(str.substring(0, curOffset + 1)))) {
  4469. // found stop symbol
  4470. startIndex = curOffset + 1;
  4471. break;
  4472. }
  4473. }
  4474. }
  4475. if(startIndex != -1 && !textCount && !braceCount && !groupCount)
  4476. // found something, remove some invalid symbols from the
  4477. // beginning and return abbreviation
  4478. return str.substring(startIndex).replace(/^[\*\+\>\^]+/, '');
  4479. else return '';
  4480. },
  4481. /**
  4482. * Gets image size from image byte stream.
  4483. * @author http://romeda.org/rePublish/
  4484. * @param {String} stream Image byte stream (use <code>IEmmetFile.read()</code>)
  4485. * @return {Object} Object with <code>width</code> and <code>height</code> properties
  4486. */
  4487. getImageSize: function(stream) {
  4488. var pngMagicNum = "\211PNG\r\n\032\n",
  4489. jpgMagicNum = "\377\330",
  4490. gifMagicNum = "GIF8",
  4491. nextByte = function() {
  4492. return stream.charCodeAt(pos++);
  4493. };
  4494. if(stream.substr(0, 8) === pngMagicNum) {
  4495. // PNG. Easy peasy.
  4496. var pos = stream.indexOf('IHDR') + 4;
  4497. return {
  4498. width: (nextByte() << 24) | (nextByte() << 16) | (nextByte() << 8) | nextByte(),
  4499. height: (nextByte() << 24) | (nextByte() << 16) | (nextByte() << 8) | nextByte()
  4500. };
  4501. } else if(stream.substr(0, 4) === gifMagicNum) {
  4502. pos = 6;
  4503. return {
  4504. width: nextByte() | (nextByte() << 8),
  4505. height: nextByte() | (nextByte() << 8)
  4506. };
  4507. } else if(stream.substr(0, 2) === jpgMagicNum) {
  4508. pos = 2;
  4509. var l = stream.length;
  4510. while(pos < l) {
  4511. if(nextByte() != 0xFF) return;
  4512. var marker = nextByte();
  4513. if(marker == 0xDA) break;
  4514. var size = (nextByte() << 8) | nextByte();
  4515. if(marker >= 0xC0 && marker <= 0xCF && !(marker & 0x4) && !(marker & 0x8)) {
  4516. pos += 1;
  4517. return {
  4518. height: (nextByte() << 8) | nextByte(),
  4519. width: (nextByte() << 8) | nextByte()
  4520. };
  4521. } else {
  4522. pos += size - 2;
  4523. }
  4524. }
  4525. }
  4526. },
  4527. /**
  4528. * Captures context XHTML element from editor under current caret position.
  4529. * This node can be used as a helper for abbreviation extraction
  4530. * @param {IEmmetEditor} editor
  4531. * @returns {Object}
  4532. */
  4533. captureContext: function(editor) {
  4534. var allowedSyntaxes = {
  4535. 'html': 1,
  4536. 'xml': 1,
  4537. 'xsl': 1
  4538. };
  4539. var syntax = String(editor.getSyntax());
  4540. if(syntax in allowedSyntaxes) {
  4541. var tags = require('html_matcher').getTags(
  4542. String(editor.getContent()), editor.getCaretPos(), String(editor.getProfileName()));
  4543. if(tags && tags[0] && tags[0].type == 'tag') {
  4544. var reAttr = /([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
  4545. var startTag = tags[0];
  4546. var tagAttrs = startTag.full_tag.replace(/^<[\w\-\:]+/, '');
  4547. var contextNode = {
  4548. name: startTag.name,
  4549. attributes: []
  4550. };
  4551. // parse attributes
  4552. var m;
  4553. while(m = reAttr.exec(tagAttrs)) {
  4554. contextNode.attributes.push({
  4555. name: m[1],
  4556. value: m[2]
  4557. });
  4558. }
  4559. return contextNode;
  4560. }
  4561. }
  4562. return null;
  4563. },
  4564. /**
  4565. * Find expression bounds in current editor at caret position.
  4566. * On each character a <code>fn</code> function will be called and must
  4567. * return <code>true</code> if current character meets requirements,
  4568. * <code>false</code> otherwise
  4569. * @param {IEmmetEditor} editor
  4570. * @param {Function} fn Function to test each character of expression
  4571. * @return {Range}
  4572. */
  4573. findExpressionBounds: function(editor, fn) {
  4574. var content = String(editor.getContent());
  4575. var il = content.length;
  4576. var exprStart = editor.getCaretPos() - 1;
  4577. var exprEnd = exprStart + 1;
  4578. // start by searching left
  4579. while(exprStart >= 0 && fn(content.charAt(exprStart), exprStart, content)) exprStart--;
  4580. // then search right
  4581. while(exprEnd < il && fn(content.charAt(exprEnd), exprEnd, content)) exprEnd++;
  4582. if(exprEnd > exprStart) {
  4583. return require('range').create([++exprStart, exprEnd]);
  4584. }
  4585. },
  4586. /**
  4587. * @param {IEmmetEditor} editor
  4588. * @param {Object} data
  4589. * @returns {Boolean}
  4590. */
  4591. compoundUpdate: function(editor, data) {
  4592. if(data) {
  4593. var sel = editor.getSelectionRange();
  4594. editor.replaceContent(data.data, data.start, data.end, true);
  4595. editor.createSelection(data.caret, data.caret + sel.end - sel.start);
  4596. return true;
  4597. }
  4598. return false;
  4599. }
  4600. };
  4601. });
  4602. /**
  4603. * Utility functions to work with <code>AbbreviationNode</code> as HTML element
  4604. * @param {Function} require
  4605. * @param {Underscore} _
  4606. */
  4607. emmet.define('abbreviationUtils', function(require, _) {
  4608. return {
  4609. /**
  4610. * Check if passed abbreviation node has matched snippet resource
  4611. * @param {AbbreviationNode} node
  4612. * @returns {Boolean}
  4613. * @memberOf abbreviationUtils
  4614. */
  4615. isSnippet: function(node) {
  4616. return require('elements').is(node.matchedResource(), 'snippet');
  4617. },
  4618. /**
  4619. * Test if passed node is unary (no closing tag)
  4620. * @param {AbbreviationNode} node
  4621. * @return {Boolean}
  4622. */
  4623. isUnary: function(node) {
  4624. var r = node.matchedResource();
  4625. if(node.children.length || this.isSnippet(node)) return false;
  4626. return r && r.is_empty || require('tagName').isEmptyElement(node.name());
  4627. },
  4628. /**
  4629. * Test if passed node is inline-level (like &lt;strong&gt;, &lt;img&gt;)
  4630. * @param {AbbreviationNode} node
  4631. * @return {Boolean}
  4632. */
  4633. isInline: function(node) {
  4634. return node.isTextNode() || !node.name() || require('tagName').isInlineLevel(node.name());
  4635. },
  4636. /**
  4637. * Test if passed node is block-level
  4638. * @param {AbbreviationNode} node
  4639. * @return {Boolean}
  4640. */
  4641. isBlock: function(node) {
  4642. return require('elements').is(node.matchedResource(), 'snippet') || !this.isInline(node);
  4643. },
  4644. /**
  4645. * This function tests if passed node content contains HTML tags.
  4646. * This function is mostly used for output formatting
  4647. * @param {AbbreviationNode} node
  4648. * @returns {Boolean}
  4649. */
  4650. hasTagsInContent: function(node) {
  4651. return require('utils').matchesTag(node.content);
  4652. },
  4653. /**
  4654. * Test if current element contains block-level children
  4655. * @param {AbbreviationNode} node
  4656. * @return {Boolean}
  4657. */
  4658. hasBlockChildren: function(node) {
  4659. return(this.hasTagsInContent(node) && this.isBlock(node)) || _.any(node.children, function(child) {
  4660. return this.isBlock(child);
  4661. }, this);
  4662. },
  4663. /**
  4664. * Utility function that inserts content instead of <code>${child}</code>
  4665. * variables on <code>text</code>
  4666. * @param {String} text Text where child content should be inserted
  4667. * @param {String} childContent Content to insert
  4668. * @param {Object} options
  4669. * @returns {String
  4670. */
  4671. insertChildContent: function(text, childContent, options) {
  4672. options = _.extend({
  4673. keepVariable: true,
  4674. appendIfNoChild: true
  4675. }, options || {});
  4676. var childVariableReplaced = false;
  4677. var utils = require('utils');
  4678. text = utils.replaceVariables(text, function(variable, name, data) {
  4679. var output = variable;
  4680. if(name == 'child') {
  4681. // add correct indentation
  4682. output = utils.padString(childContent, utils.getLinePaddingFromPosition(text, data.start));
  4683. childVariableReplaced = true;
  4684. if(options.keepVariable) output += variable;
  4685. }
  4686. return output;
  4687. });
  4688. if(!childVariableReplaced && options.appendIfNoChild) {
  4689. text += childContent;
  4690. }
  4691. return text;
  4692. }
  4693. };
  4694. });
  4695. /**
  4696. * @author Sergey Chikuyonok (serge.che@gmail.com)
  4697. * @link http://chikuyonok.ru
  4698. */
  4699. emmet.define('base64', function(require, _) {
  4700. var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  4701. return {
  4702. /**
  4703. * Encodes data using base64 algorithm
  4704. * @author Tyler Akins (http://rumkin.com)
  4705. * @param {String} input
  4706. * @returns {String}
  4707. * @memberOf emmet.base64
  4708. */
  4709. encode: function(input) {
  4710. var output = [];
  4711. var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
  4712. var i = 0,
  4713. il = input.length,
  4714. b64 = chars;
  4715. while(i < il) {
  4716. cdp1 = input.charCodeAt(i++);
  4717. cdp2 = input.charCodeAt(i++);
  4718. cdp3 = input.charCodeAt(i++);
  4719. chr1 = cdp1 & 0xff;
  4720. chr2 = cdp2 & 0xff;
  4721. chr3 = cdp3 & 0xff;
  4722. enc1 = chr1 >> 2;
  4723. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  4724. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  4725. enc4 = chr3 & 63;
  4726. if(isNaN(cdp2)) {
  4727. enc3 = enc4 = 64;
  4728. } else if(isNaN(cdp3)) {
  4729. enc4 = 64;
  4730. }
  4731. output.push(b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4));
  4732. }
  4733. return output.join('');
  4734. },
  4735. /**
  4736. * Decodes string using MIME base64 algorithm
  4737. *
  4738. * @author Tyler Akins (http://rumkin.com)
  4739. * @param {String} data
  4740. * @return {String}
  4741. */
  4742. decode: function(data) {
  4743. var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
  4744. ac = 0,
  4745. tmpArr = [];
  4746. var b64 = chars,
  4747. il = data.length;
  4748. if(!data) {
  4749. return data;
  4750. }
  4751. data += '';
  4752. do { // unpack four hexets into three octets using index points in b64
  4753. h1 = b64.indexOf(data.charAt(i++));
  4754. h2 = b64.indexOf(data.charAt(i++));
  4755. h3 = b64.indexOf(data.charAt(i++));
  4756. h4 = b64.indexOf(data.charAt(i++));
  4757. bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
  4758. o1 = bits >> 16 & 0xff;
  4759. o2 = bits >> 8 & 0xff;
  4760. o3 = bits & 0xff;
  4761. if(h3 == 64) {
  4762. tmpArr[ac++] = String.fromCharCode(o1);
  4763. } else if(h4 == 64) {
  4764. tmpArr[ac++] = String.fromCharCode(o1, o2);
  4765. } else {
  4766. tmpArr[ac++] = String.fromCharCode(o1, o2, o3);
  4767. }
  4768. } while (i < il);
  4769. return tmpArr.join('');
  4770. }
  4771. };
  4772. });
  4773. /**
  4774. * @author Sergey Chikuyonok (serge.che@gmail.com)
  4775. * @link http://chikuyonok.ru
  4776. */
  4777. (function() {
  4778. // Regular Expressions for parsing tags and attributes
  4779. var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  4780. end_tag = /^<\/([\w\:\-]+)[^>]*>/,
  4781. attr = /([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
  4782. // Empty Elements - HTML 4.01
  4783. var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
  4784. // Block Elements - HTML 4.01
  4785. var block = makeMap("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
  4786. // Inline Elements - HTML 4.01
  4787. var inline = makeMap("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");
  4788. // Elements that you can, intentionally, leave open
  4789. // (and which close themselves)
  4790. var close_self = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
  4791. /** Current matching mode */
  4792. var cur_mode = 'xhtml';
  4793. /** Last matched HTML pair */
  4794. var last_match = {
  4795. opening_tag: null,
  4796. // tag() or comment() object
  4797. closing_tag: null,
  4798. // tag() or comment() object
  4799. start_ix: -1,
  4800. end_ix: -1
  4801. };
  4802. function setMode(new_mode) {
  4803. if(!new_mode || new_mode != 'html') new_mode = 'xhtml';
  4804. cur_mode = new_mode;
  4805. }
  4806. function tag(match, ix) {
  4807. var name = match[1].toLowerCase();
  4808. return {
  4809. name: name,
  4810. full_tag: match[0],
  4811. start: ix,
  4812. end: ix + match[0].length,
  4813. unary: Boolean(match[3]) || (name in empty && cur_mode == 'html'),
  4814. has_close: Boolean(match[3]),
  4815. type: 'tag',
  4816. close_self: (name in close_self && cur_mode == 'html')
  4817. };
  4818. }
  4819. function comment(start, end) {
  4820. return {
  4821. start: start,
  4822. end: end,
  4823. type: 'comment'
  4824. };
  4825. }
  4826. function makeMap(str) {
  4827. var obj = {},
  4828. items = str.split(",");
  4829. for(var i = 0; i < items.length; i++)
  4830. obj[items[i]] = true;
  4831. return obj;
  4832. }
  4833. /**
  4834. * Makes selection ranges for matched tag pair
  4835. * @param {tag} opening_tag
  4836. * @param {tag} closing_tag
  4837. * @param {Number} ix
  4838. */
  4839. function makeRange(opening_tag, closing_tag, ix) {
  4840. ix = ix || 0;
  4841. var start_ix = -1,
  4842. end_ix = -1;
  4843. if(opening_tag && !closing_tag) { // unary element
  4844. start_ix = opening_tag.start;
  4845. end_ix = opening_tag.end;
  4846. } else if(opening_tag && closing_tag) { // complete element
  4847. if(
  4848. (opening_tag.start < ix && opening_tag.end > ix) || (closing_tag.start <= ix && closing_tag.end > ix)) {
  4849. start_ix = opening_tag.start;
  4850. end_ix = closing_tag.end;
  4851. } else {
  4852. start_ix = opening_tag.end;
  4853. end_ix = closing_tag.start;
  4854. }
  4855. }
  4856. return [start_ix, end_ix];
  4857. }
  4858. /**
  4859. * Save matched tag for later use and return found indexes
  4860. * @param {tag} opening_tag
  4861. * @param {tag} closing_tag
  4862. * @param {Number} ix
  4863. * @return {Array}
  4864. */
  4865. function saveMatch(opening_tag, closing_tag, ix) {
  4866. ix = ix || 0;
  4867. last_match.opening_tag = opening_tag;
  4868. last_match.closing_tag = closing_tag;
  4869. var range = makeRange(opening_tag, closing_tag, ix);
  4870. last_match.start_ix = range[0];
  4871. last_match.end_ix = range[1];
  4872. return last_match.start_ix != -1 ? [last_match.start_ix, last_match.end_ix] : null;
  4873. }
  4874. /**
  4875. * Handle unary tag: find closing tag if needed
  4876. * @param {String} text
  4877. * @param {Number} ix
  4878. * @param {tag} open_tag
  4879. * @return {tag|null} Closing tag (or null if not found)
  4880. */
  4881. function handleUnaryTag(text, ix, open_tag) {
  4882. if(open_tag.has_close) return null;
  4883. else {
  4884. // TODO finish this method
  4885. }
  4886. }
  4887. /**
  4888. * Search for matching tags in <code>html</code>, starting from
  4889. * <code>start_ix</code> position
  4890. * @param {String} html Code to search
  4891. * @param {Number} start_ix Character index where to start searching pair
  4892. * (commonly, current caret position)
  4893. * @param {Function} action Function that creates selection range
  4894. * @return {Array}
  4895. */
  4896. function findPair(html, start_ix, mode, action) {
  4897. action = action || makeRange;
  4898. setMode(mode);
  4899. var forward_stack = [],
  4900. backward_stack = [],
  4901. /** @type {tag()} */
  4902. opening_tag = null,
  4903. /** @type {tag()} */
  4904. closing_tag = null,
  4905. html_len = html.length,
  4906. m, ix, tmp_tag;
  4907. forward_stack.last = backward_stack.last = function() {
  4908. return this[this.length - 1];
  4909. };
  4910. function hasMatch(str, start) {
  4911. if(arguments.length == 1) start = ix;
  4912. return html.substr(start, str.length) == str;
  4913. }
  4914. function searchCommentStart(from) {
  4915. while(from--) {
  4916. if(html.charAt(from) == '<' && hasMatch('<!--', from)) break;
  4917. }
  4918. return from;
  4919. }
  4920. // find opening tag
  4921. ix = start_ix;
  4922. while(ix-- && ix >= 0) {
  4923. var ch = html.charAt(ix);
  4924. if(ch == '<') {
  4925. var check_str = html.substring(ix, html_len);
  4926. if((m = check_str.match(end_tag))) { // found closing tag
  4927. tmp_tag = tag(m, ix);
  4928. if(tmp_tag.start < start_ix && tmp_tag.end > start_ix) // direct hit on searched closing tag
  4929. closing_tag = tmp_tag;
  4930. else backward_stack.push(tmp_tag);
  4931. } else if((m = check_str.match(start_tag))) { // found opening tag
  4932. tmp_tag = tag(m, ix);
  4933. if(tmp_tag.unary) {
  4934. if(tmp_tag.start < start_ix && tmp_tag.end > start_ix) // exact match
  4935. // TODO handle unary tag
  4936. return action(tmp_tag, null, start_ix);
  4937. } else if(backward_stack.last() && backward_stack.last().name == tmp_tag.name) {
  4938. backward_stack.pop();
  4939. } else { // found nearest unclosed tag
  4940. opening_tag = tmp_tag;
  4941. break;
  4942. }
  4943. } else if(check_str.indexOf('<!--') == 0) { // found comment start
  4944. var end_ix = check_str.search('-->') + ix + 3;
  4945. if(ix < start_ix && end_ix >= start_ix) return action(comment(ix, end_ix));
  4946. }
  4947. } else if(ch == '-' && hasMatch('-->')) { // found comment end
  4948. // search left until comment start is reached
  4949. ix = searchCommentStart(ix);
  4950. }
  4951. }
  4952. if(!opening_tag) return action(null);
  4953. // find closing tag
  4954. if(!closing_tag) {
  4955. for(ix = start_ix; ix < html_len; ix++) {
  4956. var ch = html.charAt(ix);
  4957. if(ch == '<') {
  4958. var check_str = html.substring(ix, html_len);
  4959. if((m = check_str.match(start_tag))) { // found opening tag
  4960. tmp_tag = tag(m, ix);
  4961. if(!tmp_tag.unary) forward_stack.push(tmp_tag);
  4962. } else if((m = check_str.match(end_tag))) { // found closing tag
  4963. var tmp_tag = tag(m, ix);
  4964. if(forward_stack.last() && forward_stack.last().name == tmp_tag.name) forward_stack.pop();
  4965. else { // found matched closing tag
  4966. closing_tag = tmp_tag;
  4967. break;
  4968. }
  4969. } else if(hasMatch('<!--')) { // found comment
  4970. ix += check_str.search('-->') + 2;
  4971. }
  4972. } else if(ch == '-' && hasMatch('-->')) {
  4973. // looks like cursor was inside comment with invalid HTML
  4974. if(!forward_stack.last() || forward_stack.last().type != 'comment') {
  4975. var end_ix = ix + 3;
  4976. return action(comment(searchCommentStart(ix), end_ix));
  4977. }
  4978. }
  4979. }
  4980. }
  4981. return action(opening_tag, closing_tag, start_ix);
  4982. }
  4983. /**
  4984. * Search for matching tags in <code>html</code>, starting
  4985. * from <code>start_ix</code> position. The result is automatically saved in
  4986. * <code>last_match</code> property
  4987. *
  4988. * @return {Array|null}
  4989. */
  4990. var HTMLPairMatcher = function( /* String */ html, /* Number */ start_ix, /* */ mode) {
  4991. return findPair(html, start_ix, mode, saveMatch);
  4992. };
  4993. HTMLPairMatcher.start_tag = start_tag;
  4994. HTMLPairMatcher.end_tag = end_tag;
  4995. /**
  4996. * Search for matching tags in <code>html</code>, starting from
  4997. * <code>start_ix</code> position. The difference between
  4998. * <code>HTMLPairMatcher</code> function itself is that <code>find</code>
  4999. * method doesn't save matched result in <code>last_match</code> property.
  5000. * This method is generally used for lookups
  5001. */
  5002. HTMLPairMatcher.find = function(html, start_ix, mode) {
  5003. return findPair(html, start_ix, mode);
  5004. };
  5005. /**
  5006. * Search for matching tags in <code>html</code>, starting from
  5007. * <code>start_ix</code> position. The difference between
  5008. * <code>HTMLPairMatcher</code> function itself is that <code>getTags</code>
  5009. * method doesn't save matched result in <code>last_match</code> property
  5010. * and returns array of opening and closing tags
  5011. * This method is generally used for lookups
  5012. */
  5013. HTMLPairMatcher.getTags = function(html, start_ix, mode) {
  5014. return findPair(html, start_ix, mode, function(opening_tag, closing_tag) {
  5015. return [opening_tag, closing_tag];
  5016. });
  5017. };
  5018. HTMLPairMatcher.last_match = last_match;
  5019. try {
  5020. emmet.define('html_matcher', function() {
  5021. return HTMLPairMatcher;
  5022. });
  5023. } catch(e) {}
  5024. })();
  5025. /**
  5026. * Utility module for handling tabstops tokens generated by Emmet's
  5027. * "Expand Abbreviation" action. The main <code>extract</code> method will take
  5028. * raw text (for example: <i>${0} some ${1:text}</i>), find all tabstops
  5029. * occurrences, replace them with tokens suitable for your editor of choice and
  5030. * return object with processed text and list of found tabstops and their ranges.
  5031. * For sake of portability (Objective-C/Java) the tabstops list is a plain
  5032. * sorted array with plain objects.
  5033. *
  5034. * Placeholders with the same are meant to be <i>linked</i> in your editor.
  5035. * @param {Function} require
  5036. * @param {Underscore} _
  5037. */
  5038. emmet.define('tabStops', function(require, _) {
  5039. /**
  5040. * Global placeholder value, automatically incremented by
  5041. * <code>variablesResolver()</code> function
  5042. */
  5043. var startPlaceholderNum = 100;
  5044. var tabstopIndex = 0;
  5045. var defaultOptions = {
  5046. replaceCarets: false,
  5047. escape: function(ch) {
  5048. return '\\' + ch;
  5049. },
  5050. tabstop: function(data) {
  5051. return data.token;
  5052. },
  5053. variable: function(data) {
  5054. return data.token;
  5055. }
  5056. };
  5057. // XXX register output processor that will upgrade tabstops of parsed node
  5058. // in order to prevent tabstop index conflicts
  5059. require('abbreviationParser').addOutputProcessor(function(text, node, type) {
  5060. var maxNum = 0;
  5061. var tabstops = require('tabStops');
  5062. var utils = require('utils');
  5063. // upgrade tabstops
  5064. text = tabstops.processText(text, {
  5065. tabstop: function(data) {
  5066. var group = parseInt(data.group);
  5067. if(group == 0) return '${0}';
  5068. if(group > maxNum) maxNum = group;
  5069. if(data.placeholder) return '${' + (group + tabstopIndex) + ':' + data.placeholder + '}';
  5070. else return '${' + (group + tabstopIndex) + '}';
  5071. }
  5072. });
  5073. // resolve variables
  5074. text = utils.replaceVariables(text, tabstops.variablesResolver(node));
  5075. tabstopIndex += maxNum + 1;
  5076. return text;
  5077. });
  5078. return {
  5079. /**
  5080. * Main function that looks for a tabstops in provided <code>text</code>
  5081. * and returns a processed version of <code>text</code> with expanded
  5082. * placeholders and list of tabstops found.
  5083. * @param {String} text Text to process
  5084. * @param {Object} options List of processor options:<br>
  5085. *
  5086. * <b>replaceCarets</b> : <code>Boolean</code> — replace all default
  5087. * caret placeholders (like <i>{%::emmet-caret::%}</i>) with <i>${0:caret}</i><br>
  5088. *
  5089. * <b>escape</b> : <code>Function</code> — function that handle escaped
  5090. * characters (mostly '$'). By default, it returns the character itself
  5091. * to be displayed as is in output, but sometimes you will use
  5092. * <code>extract</code> method as intermediate solution for further
  5093. * processing and want to keep character escaped. Thus, you should override
  5094. * <code>escape</code> method to return escaped symbol (e.g. '\\$')<br>
  5095. *
  5096. * <b>tabstop</b> : <code>Function</code> – a tabstop handler. Receives
  5097. * a single argument – an object describing token: its position, number
  5098. * group, placeholder and token itself. Should return a replacement
  5099. * string that will appear in final output
  5100. *
  5101. * <b>variable</b> : <code>Function</code> – variable handler. Receives
  5102. * a single argument – an object describing token: its position, name
  5103. * and original token itself. Should return a replacement
  5104. * string that will appear in final output
  5105. *
  5106. * @returns {Object} Object with processed <code>text</code> property
  5107. * and array of <code>tabstops</code> found
  5108. * @memberOf tabStops
  5109. */
  5110. extract: function(text, options) {
  5111. // prepare defaults
  5112. var utils = require('utils');
  5113. var placeholders = {
  5114. carets: ''
  5115. };
  5116. var marks = [];
  5117. options = _.extend({}, defaultOptions, options, {
  5118. tabstop: function(data) {
  5119. var token = data.token;
  5120. var ret = '';
  5121. if(data.placeholder == 'cursor') {
  5122. marks.push({
  5123. start: data.start,
  5124. end: data.start + token.length,
  5125. group: 'carets',
  5126. value: ''
  5127. });
  5128. } else {
  5129. // unify placeholder value for single group
  5130. if('placeholder' in data) placeholders[data.group] = data.placeholder;
  5131. if(data.group in placeholders) ret = placeholders[data.group];
  5132. marks.push({
  5133. start: data.start,
  5134. end: data.start + token.length,
  5135. group: data.group,
  5136. value: ret
  5137. });
  5138. }
  5139. return token;
  5140. }
  5141. });
  5142. if(options.replaceCarets) {
  5143. text = text.replace(new RegExp(utils.escapeForRegexp(utils.getCaretPlaceholder()), 'g'), '${0:cursor}');
  5144. }
  5145. // locate tabstops and unify group's placeholders
  5146. text = this.processText(text, options);
  5147. // now, replace all tabstops with placeholders
  5148. var buf = utils.stringBuilder(),
  5149. lastIx = 0;
  5150. var tabStops = _.map(marks, function(mark) {
  5151. buf.append(text.substring(lastIx, mark.start));
  5152. var pos = buf.length;
  5153. var ph = placeholders[mark.group] || '';
  5154. buf.append(ph);
  5155. lastIx = mark.end;
  5156. return {
  5157. group: mark.group,
  5158. start: pos,
  5159. end: pos + ph.length
  5160. };
  5161. });
  5162. buf.append(text.substring(lastIx));
  5163. return {
  5164. text: buf.toString(),
  5165. tabstops: _.sortBy(tabStops, 'start')
  5166. };
  5167. },
  5168. /**
  5169. * Text processing routine. Locates escaped characters and tabstops and
  5170. * replaces them with values returned by handlers defined in
  5171. * <code>options</code>
  5172. * @param {String} text
  5173. * @param {Object} options See <code>extract</code> method options
  5174. * description
  5175. * @returns {String}
  5176. */
  5177. processText: function(text, options) {
  5178. options = _.extend({}, defaultOptions, options);
  5179. var buf = require('utils').stringBuilder(); /** @type StringStream */
  5180. var stream = require('stringStream').create(text);
  5181. var ch, m, a;
  5182. while(ch = stream.next()) {
  5183. if(ch == '\\' && !stream.eol()) {
  5184. // handle escaped character
  5185. buf.append(options.escape(stream.next()));
  5186. continue;
  5187. }
  5188. a = ch;
  5189. if(ch == '$') {
  5190. // looks like a tabstop
  5191. stream.start = stream.pos - 1;
  5192. if(m = stream.match(/^[0-9]+/)) {
  5193. // it's $N
  5194. a = options.tabstop({
  5195. start: buf.length,
  5196. group: stream.current().substr(1),
  5197. token: stream.current()
  5198. });
  5199. } else if(m = stream.match(/^\{([a-z_\-][\w\-]*)\}/)) {
  5200. // ${variable}
  5201. a = options.variable({
  5202. start: buf.length,
  5203. name: m[1],
  5204. token: stream.current()
  5205. });
  5206. } else if(m = stream.match(/^\{([0-9]+)(:.+?)?\}/)) {
  5207. // ${N:value} or ${N} placeholder
  5208. var obj = {
  5209. start: buf.length,
  5210. group: m[1],
  5211. token: stream.current()
  5212. };
  5213. if(m[2]) {
  5214. obj.placeholder = m[2].substr(1);
  5215. }
  5216. a = options.tabstop(obj);
  5217. }
  5218. }
  5219. buf.append(a);
  5220. }
  5221. return buf.toString();
  5222. },
  5223. /**
  5224. * Upgrades tabstops in output node in order to prevent naming conflicts
  5225. * @param {AbbreviationNode} node
  5226. * @param {Number} offset Tab index offset
  5227. * @returns {Number} Maximum tabstop index in element
  5228. */
  5229. upgrade: function(node, offset) {
  5230. var maxNum = 0;
  5231. var options = {
  5232. tabstop: function(data) {
  5233. var group = parseInt(data.group);
  5234. if(group > maxNum) maxNum = group;
  5235. if(data.placeholder) return '${' + (group + offset) + ':' + data.placeholder + '}';
  5236. else return '${' + (group + offset) + '}';
  5237. }
  5238. };
  5239. _.each(['start', 'end', 'content'], function(p) {
  5240. node[p] = this.processText(node[p], options);
  5241. }, this);
  5242. return maxNum;
  5243. },
  5244. /**
  5245. * Helper function that produces a callback function for
  5246. * <code>replaceVariables()</code> method from {@link utils}
  5247. * module. This callback will replace variable definitions (like
  5248. * ${var_name}) with their value defined in <i>resource</i> module,
  5249. * or outputs tabstop with variable name otherwise.
  5250. * @param {AbbreviationNode} node Context node
  5251. * @returns {Function}
  5252. */
  5253. variablesResolver: function(node) {
  5254. var placeholderMemo = {};
  5255. var res = require('resources');
  5256. return function(str, varName) {
  5257. // do not mark `child` variable as placeholder – it‘s a reserved
  5258. // variable name
  5259. if(varName == 'child') return str;
  5260. if(varName == 'cursor') return require('utils').getCaretPlaceholder();
  5261. var attr = node.attribute(varName);
  5262. if(!_.isUndefined(attr)) return attr;
  5263. var varValue = res.getVariable(varName);
  5264. if(varValue) return varValue;
  5265. // output as placeholder
  5266. if(!placeholderMemo[varName]) placeholderMemo[varName] = startPlaceholderNum++;
  5267. return '${' + placeholderMemo[varName] + ':' + varName + '}';
  5268. };
  5269. },
  5270. resetPlaceholderCounter: function() {
  5271. console.log('deprecated');
  5272. startPlaceholderNum = 100;
  5273. },
  5274. /**
  5275. * Resets global tabstop index. When parsed tree is converted to output
  5276. * string (<code>AbbreviationNode.toString()</code>), all tabstops
  5277. * defined in snippets and elements are upgraded in order to prevent
  5278. * naming conflicts of nested. For example, <code>${1}</code> of a node
  5279. * should not be linked with the same placehilder of the child node.
  5280. * By default, <code>AbbreviationNode.toString()</code> automatically
  5281. * upgrades tabstops of the same index for each node and writes maximum
  5282. * tabstop index into the <code>tabstopIndex</code> variable. To keep
  5283. * this variable at reasonable value, it is recommended to call
  5284. * <code>resetTabstopIndex()</code> method each time you expand variable
  5285. * @returns
  5286. */
  5287. resetTabstopIndex: function() {
  5288. tabstopIndex = 0;
  5289. startPlaceholderNum = 100;
  5290. }
  5291. };
  5292. });
  5293. /**
  5294. * Common module's preferences storage. This module
  5295. * provides general storage for all module preferences, their description and
  5296. * default values.<br><br>
  5297. *
  5298. * This module can also be used to list all available properties to create
  5299. * UI for updating properties
  5300. *
  5301. * @memberOf __preferencesDefine
  5302. * @constructor
  5303. * @param {Function} require
  5304. * @param {Underscore} _
  5305. */
  5306. emmet.define('preferences', function(require, _) {
  5307. var preferences = {};
  5308. var defaults = {};
  5309. var _dbgDefaults = null;
  5310. var _dbgPreferences = null;
  5311. function toBoolean(val) {
  5312. if(_.isString(val)) {
  5313. val = val.toLowerCase();
  5314. return val == 'yes' || val == 'true' || val == '1';
  5315. }
  5316. return !!val;
  5317. }
  5318. function isValueObj(obj) {
  5319. return _.isObject(obj) && 'value' in obj && _.keys(obj).length < 3;
  5320. }
  5321. return {
  5322. /**
  5323. * Creates new preference item with default value
  5324. * @param {String} name Preference name. You can also pass object
  5325. * with many options
  5326. * @param {Object} value Preference default value
  5327. * @param {String} description Item textual description
  5328. * @memberOf preferences
  5329. */
  5330. define: function(name, value, description) {
  5331. var prefs = name;
  5332. if(_.isString(name)) {
  5333. prefs = {};
  5334. prefs[name] = {
  5335. value: value,
  5336. description: description
  5337. };
  5338. }
  5339. _.each(prefs, function(v, k) {
  5340. defaults[k] = isValueObj(v) ? v : {
  5341. value: v
  5342. };
  5343. });
  5344. },
  5345. /**
  5346. * Updates preference item value. Preference value should be defined
  5347. * first with <code>define</code> method.
  5348. * @param {String} name Preference name. You can also pass object
  5349. * with many options
  5350. * @param {Object} value Preference default value
  5351. * @memberOf preferences
  5352. */
  5353. set: function(name, value) {
  5354. var prefs = name;
  5355. if(_.isString(name)) {
  5356. prefs = {};
  5357. prefs[name] = value;
  5358. }
  5359. _.each(prefs, function(v, k) {
  5360. if(!(k in defaults)) {
  5361. throw 'Property "' + k + '" is not defined. You should define it first with `define` method of current module';
  5362. }
  5363. // do not set value if it equals to default value
  5364. if(v !== defaults[k].value) {
  5365. // make sure we have value of correct type
  5366. switch(typeof defaults[k].value) {
  5367. case 'boolean':
  5368. v = toBoolean(v);
  5369. break;
  5370. case 'number':
  5371. v = parseInt(v + '', 10) || 0;
  5372. break;
  5373. default:
  5374. // convert to string
  5375. v += '';
  5376. }
  5377. preferences[k] = v;
  5378. } else if(k in preferences) {
  5379. delete preferences[k];
  5380. }
  5381. });
  5382. },
  5383. /**
  5384. * Returns preference value
  5385. * @param {String} name
  5386. * @returns {String} Returns <code>undefined</code> if preference is
  5387. * not defined
  5388. */
  5389. get: function(name) {
  5390. if(name in preferences) return preferences[name];
  5391. if(name in defaults) return defaults[name].value;
  5392. return void 0;
  5393. },
  5394. /**
  5395. * Returns comma-separated preference value as array of values
  5396. * @param {String} name
  5397. * @returns {Array} Returns <code>undefined</code> if preference is
  5398. * not defined, <code>null</code> if string cannot be converted to array
  5399. */
  5400. getArray: function(name) {
  5401. var val = this.get(name);
  5402. if(!_.isUndefined(val)) {
  5403. val = _.map(val.split(','), require('utils').trim);
  5404. if(!val.length) val = null;
  5405. }
  5406. return val;
  5407. },
  5408. /**
  5409. * Returns comma and colon-separated preference value as dictionary
  5410. * @param {String} name
  5411. * @returns {Object}
  5412. */
  5413. getDict: function(name) {
  5414. var result = {};
  5415. _.each(this.getArray(name), function(val) {
  5416. var parts = val.split(':');
  5417. result[parts[0]] = parts[1];
  5418. });
  5419. return result;
  5420. },
  5421. /**
  5422. * Returns description of preference item
  5423. * @param {String} name Preference name
  5424. * @returns {Object}
  5425. */
  5426. description: function(name) {
  5427. return name in defaults ? defaults[name].description : void 0;
  5428. },
  5429. /**
  5430. * Completely removes specified preference(s)
  5431. * @param {String} name Preference name (or array of names)
  5432. */
  5433. remove: function(name) {
  5434. if(!_.isArray(name)) name = [name];
  5435. _.each(name, function(key) {
  5436. if(key in preferences) delete preferences[key];
  5437. if(key in defaults) delete defaults[key];
  5438. });
  5439. },
  5440. /**
  5441. * Returns sorted list of all available properties
  5442. * @returns {Array}
  5443. */
  5444. list: function() {
  5445. return _.map(_.keys(defaults).sort(), function(key) {
  5446. return {
  5447. name: key,
  5448. value: this.get(key),
  5449. type: typeof defaults[key].value,
  5450. description: defaults[key].description
  5451. };
  5452. }, this);
  5453. },
  5454. /**
  5455. * Loads user-defined preferences from JSON
  5456. * @param {Object} json
  5457. * @returns
  5458. */
  5459. load: function(json) {
  5460. _.each(json, function(value, key) {
  5461. this.set(key, value);
  5462. }, this);
  5463. },
  5464. /**
  5465. * Returns hash of user-modified preferences
  5466. * @returns {Object}
  5467. */
  5468. exportModified: function() {
  5469. return _.clone(preferences);
  5470. },
  5471. /**
  5472. * Reset to defaults
  5473. * @returns
  5474. */
  5475. reset: function() {
  5476. preferences = {};
  5477. },
  5478. /**
  5479. * For unit testing: use empty storage
  5480. */
  5481. _startTest: function() {
  5482. _dbgDefaults = defaults;
  5483. _dbgPreferences = preferences;
  5484. defaults = {};
  5485. preferences = {};
  5486. },
  5487. /**
  5488. * For unit testing: restore original storage
  5489. */
  5490. _stopTest: function() {
  5491. defaults = _dbgDefaults;
  5492. preferences = _dbgPreferences;
  5493. }
  5494. };
  5495. });
  5496. /**
  5497. * Module for handling filters
  5498. * @param {Function} require
  5499. * @param {Underscore} _
  5500. * @author Sergey Chikuyonok (serge.che@gmail.com) <http://chikuyonok.ru>
  5501. */
  5502. emmet.define('filters', function(require, _) { /** List of registered filters */
  5503. var registeredFilters = {};
  5504. /** Filters that will be applied for unknown syntax */
  5505. var basicFilters = 'html';
  5506. function list(filters) {
  5507. if(!filters) return [];
  5508. if(_.isString(filters)) return filters.split(/[\|,]/g);
  5509. return filters;
  5510. }
  5511. return {
  5512. /**
  5513. * Register new filter
  5514. * @param {String} name Filter name
  5515. * @param {Function} fn Filter function
  5516. */
  5517. add: function(name, fn) {
  5518. registeredFilters[name] = fn;
  5519. },
  5520. /**
  5521. * Apply filters for final output tree
  5522. * @param {AbbreviationNode} tree Output tree
  5523. * @param {Array} filters List of filters to apply. Might be a
  5524. * <code>String</code>
  5525. * @param {Object} profile Output profile, defined in <i>profile</i>
  5526. * module. Filters defined it profile are not used, <code>profile</code>
  5527. * is passed to filter function
  5528. * @memberOf emmet.filters
  5529. * @returns {AbbreviationNode}
  5530. */
  5531. apply: function(tree, filters, profile) {
  5532. var utils = require('utils');
  5533. profile = require('profile').get(profile);
  5534. _.each(list(filters), function(filter) {
  5535. var name = utils.trim(filter.toLowerCase());
  5536. if(name && name in registeredFilters) {
  5537. tree = registeredFilters[name](tree, profile);
  5538. }
  5539. });
  5540. return tree;
  5541. },
  5542. /**
  5543. * Composes list of filters that should be applied to a tree, based on
  5544. * passed data
  5545. * @param {String} syntax Syntax name ('html', 'css', etc.)
  5546. * @param {Object} profile Output profile
  5547. * @param {String} additionalFilters List or pipe-separated
  5548. * string of additional filters to apply
  5549. * @returns {Array}
  5550. */
  5551. composeList: function(syntax, profile, additionalFilters) {
  5552. profile = require('profile').get(profile);
  5553. var filters = list(profile.filters || require('resources').findItem(syntax, 'filters') || basicFilters);
  5554. if(additionalFilters) filters = filters.concat(list(additionalFilters));
  5555. if(!filters || !filters.length)
  5556. // looks like unknown syntax, apply basic filters
  5557. filters = list(basicFilters);
  5558. return filters;
  5559. },
  5560. /**
  5561. * Extracts filter list from abbreviation
  5562. * @param {String} abbr
  5563. * @returns {Array} Array with cleaned abbreviation and list of
  5564. * extracted filters
  5565. */
  5566. extractFromAbbreviation: function(abbr) {
  5567. var filters = '';
  5568. abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1) {
  5569. filters = p1;
  5570. return '';
  5571. });
  5572. return [abbr, list(filters)];
  5573. }
  5574. };
  5575. });
  5576. /**
  5577. * Module that contains factories for element types used by Emmet
  5578. * @param {Function} require
  5579. * @param {Underscore} _
  5580. */
  5581. emmet.define('elements', function(require, _) {
  5582. var factories = {};
  5583. var reAttrs = /([\w\-]+)\s*=\s*(['"])(.*?)\2/g;
  5584. var result = {
  5585. /**
  5586. * Create new element factory
  5587. * @param {String} name Element identifier
  5588. * @param {Function} factory Function that produces element of specified
  5589. * type. The object generated by this factory is automatically
  5590. * augmented with <code>type</code> property pointing to element
  5591. * <code>name</code>
  5592. * @memberOf elements
  5593. */
  5594. add: function(name, factory) {
  5595. var that = this;
  5596. factories[name] = function() {
  5597. var elem = factory.apply(that, arguments);
  5598. if(elem) elem.type = name;
  5599. return elem;
  5600. };
  5601. },
  5602. /**
  5603. * Returns factory for specified name
  5604. * @param {String} name
  5605. * @returns {Function}
  5606. */
  5607. get: function(name) {
  5608. return factories[name];
  5609. },
  5610. /**
  5611. * Creates new element with specified type
  5612. * @param {String} name
  5613. * @returns {Object}
  5614. */
  5615. create: function(name) {
  5616. var args = [].slice.call(arguments, 1);
  5617. var factory = this.get(name);
  5618. return factory ? factory.apply(this, args) : null;
  5619. },
  5620. /**
  5621. * Check if passed element is of specified type
  5622. * @param {Object} elem
  5623. * @param {String} type
  5624. * @returns {Boolean}
  5625. */
  5626. is: function(elem, type) {
  5627. return elem && elem.type === type;
  5628. }
  5629. };
  5630. // register resource references
  5631. function commonFactory(value) {
  5632. return {
  5633. data: value
  5634. };
  5635. }
  5636. /**
  5637. * Element factory
  5638. * @param {String} elementName Name of output element
  5639. * @param {String} attrs Attributes definition. You may also pass
  5640. * <code>Array</code> where each contains object with <code>name</code>
  5641. * and <code>value</code> properties, or <code>Object</code>
  5642. * @param {Boolean} isEmpty Is expanded element should be empty
  5643. */
  5644. result.add('element', function(elementName, attrs, isEmpty) {
  5645. var ret = { /** @memberOf __emmetDataElement */
  5646. name: elementName,
  5647. is_empty: !! isEmpty
  5648. };
  5649. if(attrs) {
  5650. ret.attributes = [];
  5651. if(_.isArray(attrs)) {
  5652. ret.attributes = attrs;
  5653. } else if(_.isString(attrs)) {
  5654. var m;
  5655. while(m = reAttrs.exec(attrs)) {
  5656. ret.attributes.push({
  5657. name: m[1],
  5658. value: m[3]
  5659. });
  5660. }
  5661. } else {
  5662. _.each(attrs, function(value, name) {
  5663. ret.attributes.push({
  5664. name: name,
  5665. value: value
  5666. });
  5667. });
  5668. }
  5669. }
  5670. return ret;
  5671. });
  5672. result.add('snippet', commonFactory);
  5673. result.add('reference', commonFactory);
  5674. result.add('empty', function() {
  5675. return {};
  5676. });
  5677. return result;
  5678. });
  5679. /**
  5680. * Abstract implementation of edit tree interface.
  5681. * Edit tree is a named container of editable “name-value” child elements,
  5682. * parsed from <code>source</code>. This container provides convenient methods
  5683. * for editing/adding/removing child elements. All these update actions are
  5684. * instantly reflected in the <code>source</code> code with respect of formatting.
  5685. * <br><br>
  5686. * For example, developer can create an edit tree from CSS rule and add or
  5687. * remove properties from it–all changes will be immediately reflected in the
  5688. * original source.
  5689. * <br><br>
  5690. * All classes defined in this module should be extended the same way as in
  5691. * Backbone framework: using <code>extend</code> method to create new class and
  5692. * <code>initialize</code> method to define custom class constructor.
  5693. *
  5694. * @example
  5695. * <pre><code>
  5696. * var MyClass = require('editTree').EditElement.extend({
  5697. * initialize: function() {
  5698. * // constructor code here
  5699. * }
  5700. * });
  5701. *
  5702. * var elem = new MyClass();
  5703. * </code></pre>
  5704. *
  5705. *
  5706. * @param {Function} require
  5707. * @param {Underscore} _
  5708. * @constructor
  5709. * @memberOf __editTreeDefine
  5710. */
  5711. emmet.define('editTree', function(require, _, core) {
  5712. var range = require('range').create;
  5713. /**
  5714. * Named container of edited source
  5715. * @type EditContainer
  5716. * @param {String} source
  5717. * @param {Object} options
  5718. */
  5719. function EditContainer(source, options) {
  5720. this.options = _.extend({
  5721. offset: 0
  5722. }, options);
  5723. /**
  5724. * Source code of edited structure. All changes in the structure are
  5725. * immediately reflected into this property
  5726. */
  5727. this.source = source;
  5728. /**
  5729. * List of all editable children
  5730. * @private
  5731. */
  5732. this._children = [];
  5733. /**
  5734. * Hash of all positions of container
  5735. * @private
  5736. */
  5737. this._positions = {
  5738. name: 0
  5739. };
  5740. this.initialize.apply(this, arguments);
  5741. }
  5742. /**
  5743. * The self-propagating extend function for classes.
  5744. * @type Function
  5745. */
  5746. EditContainer.extend = core.extend;
  5747. EditContainer.prototype = {
  5748. /**
  5749. * Child class constructor
  5750. */
  5751. initialize: function() {},
  5752. /**
  5753. * Replace substring of tag's source
  5754. * @param {String} value
  5755. * @param {Number} start
  5756. * @param {Number} end
  5757. * @private
  5758. */
  5759. _updateSource: function(value, start, end) {
  5760. // create modification range
  5761. var r = range(start, _.isUndefined(end) ? 0 : end - start);
  5762. var delta = value.length - r.length();
  5763. var update = function(obj) {
  5764. _.each(obj, function(v, k) {
  5765. if(v >= r.end) obj[k] += delta;
  5766. });
  5767. };
  5768. // update affected positions of current container
  5769. update(this._positions);
  5770. // update affected positions of children
  5771. _.each(this.list(), function(item) {
  5772. update(item._positions);
  5773. });
  5774. this.source = require('utils').replaceSubstring(this.source, value, r);
  5775. },
  5776. /**
  5777. * Adds new attribute
  5778. * @param {String} name Property name
  5779. * @param {String} value Property value
  5780. * @param {Number} pos Position at which to insert new property. By
  5781. * default the property is inserted at the end of rule
  5782. * @returns {EditElement} Newly created element
  5783. */
  5784. add: function(name, value, pos) {
  5785. // this is abstract implementation
  5786. var item = new EditElement(name, value);
  5787. this._children.push(item);
  5788. return item;
  5789. },
  5790. /**
  5791. * Returns attribute object
  5792. * @param {String} name Attribute name or its index
  5793. * @returns {EditElement}
  5794. */
  5795. get: function(name) {
  5796. if(_.isNumber(name)) return this.list()[name];
  5797. if(_.isString(name)) return _.find(this.list(), function(prop) {
  5798. return prop.name() === name;
  5799. });
  5800. return name;
  5801. },
  5802. /**
  5803. * Returns all children by name or indexes
  5804. * @param {Object} name Element name(s) or indexes (<code>String</code>,
  5805. * <code>Array</code>, <code>Number</code>)
  5806. * @returns {Array}
  5807. */
  5808. getAll: function(name) {
  5809. if(!_.isArray(name)) name = [name];
  5810. // split names and indexes
  5811. var names = [],
  5812. indexes = [];
  5813. _.each(name, function(item) {
  5814. if(_.isString(item)) names.push(item);
  5815. else if(_.isNumber(item)) indexes.push(item);
  5816. });
  5817. return _.filter(this.list(), function(attribute, i) {
  5818. return _.include(indexes, i) || _.include(names, attribute.name());
  5819. });
  5820. },
  5821. /**
  5822. * Returns or updates element value. If such element doesn't exists,
  5823. * it will be created automatically and added at the end of child list.
  5824. * @param {String} name Element name or its index
  5825. * @param {String} value New element value
  5826. * @returns {String}
  5827. */
  5828. value: function(name, value, pos) {
  5829. var element = this.get(name);
  5830. if(element) return element.value(value);
  5831. if(!_.isUndefined(value)) {
  5832. // no such element — create it
  5833. return this.add(name, value, pos);
  5834. }
  5835. },
  5836. /**
  5837. * Returns all values of child elements found by <code>getAll()</code>
  5838. * method
  5839. * @param {Object} name Element name(s) or indexes (<code>String</code>,
  5840. * <code>Array</code>, <code>Number</code>)
  5841. * @returns {Array}
  5842. */
  5843. values: function(name) {
  5844. return _.map(this.getAll(name), function(element) {
  5845. return element.value();
  5846. });
  5847. },
  5848. /**
  5849. * Remove child element
  5850. * @param {String} name Property name or its index
  5851. */
  5852. remove: function(name) {
  5853. var element = this.get(name);
  5854. if(element) {
  5855. this._updateSource('', element.fullRange());
  5856. this._children = _.without(this._children, element);
  5857. }
  5858. },
  5859. /**
  5860. * Returns list of all editable child elements
  5861. * @returns {Array}
  5862. */
  5863. list: function() {
  5864. return this._children;
  5865. },
  5866. /**
  5867. * Returns index of editble child in list
  5868. * @param {Object} item
  5869. * @returns {Number}
  5870. */
  5871. indexOf: function(item) {
  5872. return _.indexOf(this.list(), this.get(item));
  5873. },
  5874. /**
  5875. * Sets or gets container name
  5876. * @param {String} val New name. If not passed, current
  5877. * name is returned
  5878. * @return {String}
  5879. */
  5880. name: function(val) {
  5881. if(!_.isUndefined(val) && this._name !== (val = String(val))) {
  5882. this._updateSource(val, this._positions.name, this._positions.name + this._name.length);
  5883. this._name = val;
  5884. }
  5885. return this._name;
  5886. },
  5887. /**
  5888. * Returns name range object
  5889. * @param {Boolean} isAbsolute Return absolute range (with respect of
  5890. * rule offset)
  5891. * @returns {Range}
  5892. */
  5893. nameRange: function(isAbsolute) {
  5894. return range(this._positions.name + (isAbsolute ? this.options.offset : 0), this.name());
  5895. },
  5896. /**
  5897. * Returns range of current source
  5898. * @param {Boolean} isAbsolute
  5899. */
  5900. range: function(isAbsolute) {
  5901. return range(isAbsolute ? this.options.offset : 0, this.toString());
  5902. },
  5903. /**
  5904. * Returns element that belongs to specified position
  5905. * @param {Number} pos
  5906. * @param {Boolean} isAbsolute
  5907. * @returns {EditElement}
  5908. */
  5909. itemFromPosition: function(pos, isAbsolute) {
  5910. return _.find(this.list(), function(elem) {
  5911. return elem.range(isAbsolute).inside(pos);
  5912. });
  5913. },
  5914. /**
  5915. * Returns source code of current container
  5916. * @returns {String}
  5917. */
  5918. toString: function() {
  5919. return this.source;
  5920. }
  5921. };
  5922. /**
  5923. * @param {EditContainer} parent
  5924. * @param {Object} nameToken
  5925. * @param {Object} valueToken
  5926. */
  5927. function EditElement(parent, nameToken, valueToken) { /** @type EditContainer */
  5928. this.parent = parent;
  5929. this._name = nameToken.value;
  5930. this._value = valueToken ? valueToken.value : '';
  5931. this._positions = {
  5932. name: nameToken.start,
  5933. value: valueToken ? valueToken.start : -1
  5934. };
  5935. this.initialize.apply(this, arguments);
  5936. }
  5937. /**
  5938. * The self-propagating extend function for classes.
  5939. * @type Function
  5940. */
  5941. EditElement.extend = core.extend;
  5942. EditElement.prototype = {
  5943. /**
  5944. * Child class constructor
  5945. */
  5946. initialize: function() {},
  5947. /**
  5948. * Make position absolute
  5949. * @private
  5950. * @param {Number} num
  5951. * @param {Boolean} isAbsolute
  5952. * @returns {Boolean}
  5953. */
  5954. _pos: function(num, isAbsolute) {
  5955. return num + (isAbsolute ? this.parent.options.offset : 0);
  5956. },
  5957. /**
  5958. * Sets of gets element value
  5959. * @param {String} val New element value. If not passed, current
  5960. * value is returned
  5961. * @returns {String}
  5962. */
  5963. value: function(val) {
  5964. if(!_.isUndefined(val) && this._value !== (val = String(val))) {
  5965. this.parent._updateSource(val, this.valueRange());
  5966. this._value = val;
  5967. }
  5968. return this._value;
  5969. },
  5970. /**
  5971. * Sets of gets element name
  5972. * @param {String} val New element name. If not passed, current
  5973. * name is returned
  5974. * @returns {String}
  5975. */
  5976. name: function(val) {
  5977. if(!_.isUndefined(val) && this._name !== (val = String(val))) {
  5978. this.parent._updateSource(val, this.nameRange());
  5979. this._name = val;
  5980. }
  5981. return this._name;
  5982. },
  5983. /**
  5984. * Returns position of element name token
  5985. * @param {Boolean} isAbsolute Return absolute position
  5986. * @returns {Number}
  5987. */
  5988. namePosition: function(isAbsolute) {
  5989. return this._pos(this._positions.name, isAbsolute);
  5990. },
  5991. /**
  5992. * Returns position of element value token
  5993. * @param {Boolean} isAbsolute Return absolute position
  5994. * @returns {Number}
  5995. */
  5996. valuePosition: function(isAbsolute) {
  5997. return this._pos(this._positions.value, isAbsolute);
  5998. },
  5999. /**
  6000. * Returns element name
  6001. * @param {Boolean} isAbsolute Return absolute range
  6002. * @returns {Range}
  6003. */
  6004. range: function(isAbsolute) {
  6005. return range(this.namePosition(isAbsolute), this.toString());
  6006. },
  6007. /**
  6008. * Returns full element range, including possible indentation
  6009. * @param {Boolean} isAbsolute Return absolute range
  6010. * @returns {Range}
  6011. */
  6012. fullRange: function(isAbsolute) {
  6013. return this.range(isAbsolute);
  6014. },
  6015. /**
  6016. * Returns element name range
  6017. * @param {Boolean} isAbsolute Return absolute range
  6018. * @returns {Range}
  6019. */
  6020. nameRange: function(isAbsolute) {
  6021. return range(this.namePosition(isAbsolute), this.name());
  6022. },
  6023. /**
  6024. * Returns element value range
  6025. * @param {Boolean} isAbsolute Return absolute range
  6026. * @returns {Range}
  6027. */
  6028. valueRange: function(isAbsolute) {
  6029. return range(this.valuePosition(isAbsolute), this.value());
  6030. },
  6031. /**
  6032. * Returns current element string representation
  6033. * @returns {String}
  6034. */
  6035. toString: function() {
  6036. return this.name() + this.value();
  6037. },
  6038. valueOf: function() {
  6039. return this.toString();
  6040. }
  6041. };
  6042. return {
  6043. EditContainer: EditContainer,
  6044. EditElement: EditElement,
  6045. /**
  6046. * Creates token that can be fed to <code>EditElement</code>
  6047. * @param {Number} start
  6048. * @param {String} value
  6049. * @param {String} type
  6050. * @returns
  6051. */
  6052. createToken: function(start, value, type) {
  6053. var obj = {
  6054. start: start || 0,
  6055. value: value || '',
  6056. type: type
  6057. };
  6058. obj.end = obj.start + obj.value.length;
  6059. return obj;
  6060. }
  6061. };
  6062. });
  6063. /**
  6064. * CSS EditTree is a module that can parse a CSS rule into a tree with
  6065. * convenient methods for adding, modifying and removing CSS properties. These
  6066. * changes can be written back to string with respect of code formatting.
  6067. *
  6068. * @memberOf __cssEditTreeDefine
  6069. * @constructor
  6070. * @param {Function} require
  6071. * @param {Underscore} _
  6072. */
  6073. emmet.define('cssEditTree', function(require, _) {
  6074. var defaultOptions = {
  6075. styleBefore: '\n\t',
  6076. styleSeparator: ': ',
  6077. offset: 0
  6078. };
  6079. var WHITESPACE_REMOVE_FROM_START = 1;
  6080. var WHITESPACE_REMOVE_FROM_END = 2;
  6081. /**
  6082. * Returns range object
  6083. * @param {Number} start
  6084. * @param {Number} len
  6085. * @returns {Range}
  6086. */
  6087. function range(start, len) {
  6088. return require('range').create(start, len);
  6089. }
  6090. /**
  6091. * Removes whitespace tokens from the array ends
  6092. * @param {Array} tokens
  6093. * @param {Number} mask Mask indicating from which end whitespace should be
  6094. * removed
  6095. * @returns {Array}
  6096. */
  6097. function trimWhitespaceTokens(tokens, mask) {
  6098. mask = mask || (WHITESPACE_REMOVE_FROM_START | WHITESPACE_REMOVE_FROM_END);
  6099. var whitespace = ['white', 'line'];
  6100. if((mask & WHITESPACE_REMOVE_FROM_END) == WHITESPACE_REMOVE_FROM_END) while(tokens.length && _.include(whitespace, _.last(tokens).type)) {
  6101. tokens.pop();
  6102. }
  6103. if((mask & WHITESPACE_REMOVE_FROM_START) == WHITESPACE_REMOVE_FROM_START) while(tokens.length && _.include(whitespace, tokens[0].type)) {
  6104. tokens.shift();
  6105. }
  6106. return tokens;
  6107. }
  6108. /**
  6109. * Helper function that searches for selector range for <code>CSSEditRule</code>
  6110. * @param {TokenIterator} it
  6111. * @returns {Range}
  6112. */
  6113. function findSelectorRange(it) {
  6114. var tokens = [],
  6115. token;
  6116. var start = it.position(),
  6117. end;
  6118. while(token = it.next()) {
  6119. if(token.type == '{') break;
  6120. tokens.push(token);
  6121. }
  6122. trimWhitespaceTokens(tokens);
  6123. if(tokens.length) {
  6124. start = tokens[0].start;
  6125. end = _.last(tokens).end;
  6126. } else {
  6127. end = start;
  6128. }
  6129. return range(start, end - start);
  6130. }
  6131. /**
  6132. * Helper function that searches for CSS property value range next to
  6133. * iterator's current position
  6134. * @param {TokenIterator} it
  6135. * @returns {Range}
  6136. */
  6137. function findValueRange(it) {
  6138. // find value start position
  6139. var skipTokens = ['white', 'line', ':'];
  6140. var tokens = [],
  6141. token, start, end;
  6142. it.nextUntil(function(tok) {
  6143. return !_.include(skipTokens, this.itemNext().type);
  6144. });
  6145. start = it.current().end;
  6146. // consume value
  6147. while(token = it.next()) {
  6148. if(token.type == '}' || token.type == ';') {
  6149. // found value end
  6150. trimWhitespaceTokens(tokens, WHITESPACE_REMOVE_FROM_START | (token.type == '}' ? WHITESPACE_REMOVE_FROM_END : 0));
  6151. if(tokens.length) {
  6152. start = tokens[0].start;
  6153. end = _.last(tokens).end;
  6154. } else {
  6155. end = start;
  6156. }
  6157. return range(start, end - start);
  6158. }
  6159. tokens.push(token);
  6160. }
  6161. // reached the end of tokens list
  6162. if(tokens.length) {
  6163. return range(tokens[0].start, _.last(tokens).end - tokens[0].start);
  6164. }
  6165. }
  6166. /**
  6167. * Finds parts of complex CSS value
  6168. * @param {String} str
  6169. * @returns {Array} Returns list of <code>Range</code>'s
  6170. */
  6171. function findParts(str) { /** @type StringStream */
  6172. var stream = require('stringStream').create(str);
  6173. var ch;
  6174. var result = [];
  6175. var sep = /[\s\u00a0,]/;
  6176. var add = function() {
  6177. stream.next();
  6178. result.push(range(stream.start, stream.current()));
  6179. stream.start = stream.pos;
  6180. };
  6181. // skip whitespace
  6182. stream.eatSpace();
  6183. stream.start = stream.pos;
  6184. while(ch = stream.next()) {
  6185. if(ch == '"' || ch == "'") {
  6186. stream.next();
  6187. if(!stream.skipTo(ch)) break;
  6188. add();
  6189. } else if(ch == '(') {
  6190. // function found, may have nested function
  6191. stream.backUp(1);
  6192. if(!stream.skipToPair('(', ')')) break;
  6193. stream.backUp(1);
  6194. add();
  6195. } else {
  6196. if(sep.test(ch)) {
  6197. result.push(range(stream.start, stream.current().length - 1));
  6198. stream.eatWhile(sep);
  6199. stream.start = stream.pos;
  6200. }
  6201. }
  6202. }
  6203. add();
  6204. return _.chain(result).filter(function(item) {
  6205. return !!item.length();
  6206. }).uniq(false, function(item) {
  6207. return item.toString();
  6208. }).value();
  6209. }
  6210. /**
  6211. * A bit hacky way to identify invalid CSS property definition: when user
  6212. * starts writing new abbreviation in CSS rule, he actually creates invalid
  6213. * CSS property definition and this method tries to identify such abbreviation
  6214. * and prevent it from being added to CSS edit tree
  6215. * @param {TokenIterator} it
  6216. */
  6217. function isValidIdentifier(it) {
  6218. // return true;
  6219. var tokens = it.tokens;
  6220. for(var i = it._i + 1, il = tokens.length; i < il; i++) {
  6221. if(tokens[i].type == ':') return true;
  6222. if(tokens[i].type == 'identifier' || tokens[i].type == 'line') return false;
  6223. }
  6224. return false;
  6225. }
  6226. /**
  6227. * @class
  6228. * @extends EditContainer
  6229. */
  6230. var CSSEditContainer = require('editTree').EditContainer.extend({
  6231. initialize: function(source, options) {
  6232. _.defaults(this.options, defaultOptions);
  6233. var editTree = require('editTree');
  6234. /** @type TokenIterator */
  6235. var it = require('tokenIterator').create(
  6236. require('cssParser').parse(source));
  6237. var selectorRange = findSelectorRange(it);
  6238. this._positions.name = selectorRange.start;
  6239. this._name = selectorRange.substring(source);
  6240. if(!it.current() || it.current().type != '{') throw 'Invalid CSS rule';
  6241. this._positions.contentStart = it.position() + 1;
  6242. // consume properties
  6243. var propertyRange, valueRange, token;
  6244. while(token = it.next()) {
  6245. if(token.type == 'identifier' && isValidIdentifier(it)) {
  6246. propertyRange = range(token);
  6247. valueRange = findValueRange(it);
  6248. var end = (it.current() && it.current().type == ';') ? range(it.current()) : range(valueRange.end, 0);
  6249. this._children.push(new CSSEditElement(this, editTree.createToken(propertyRange.start, propertyRange.substring(source)), editTree.createToken(valueRange.start, valueRange.substring(source)), editTree.createToken(end.start, end.substring(source))));
  6250. }
  6251. }
  6252. this._saveStyle();
  6253. },
  6254. /**
  6255. * Remembers all styles of properties
  6256. * @private
  6257. */
  6258. _saveStyle: function() {
  6259. var start = this._positions.contentStart;
  6260. var source = this.source;
  6261. var utils = require('utils');
  6262. _.each(this.list(), /** @param {CSSEditProperty} p */
  6263. function(p) {
  6264. p.styleBefore = source.substring(start, p.namePosition());
  6265. // a small hack here:
  6266. // Sometimes users add empty lines before properties to logically
  6267. // separate groups of properties. In this case, a blind copy of
  6268. // characters between rules may lead to undesired behavior,
  6269. // especially when current rule is duplicated or used as a donor
  6270. // to create new rule.
  6271. // To solve this issue, we‘ll take only last newline indentation
  6272. var lines = utils.splitByLines(p.styleBefore);
  6273. if(lines.length > 1) {
  6274. p.styleBefore = '\n' + _.last(lines);
  6275. }
  6276. p.styleSeparator = source.substring(p.nameRange().end, p.valuePosition());
  6277. // graceful and naive comments removal
  6278. p.styleBefore = _.last(p.styleBefore.split('*/'));
  6279. p.styleSeparator = p.styleSeparator.replace(/\/\*.*?\*\//g, '');
  6280. start = p.range().end;
  6281. });
  6282. },
  6283. /**
  6284. * Adds new CSS property
  6285. * @param {String} name Property name
  6286. * @param {String} value Property value
  6287. * @param {Number} pos Position at which to insert new property. By
  6288. * default the property is inserted at the end of rule
  6289. * @returns {CSSEditProperty}
  6290. */
  6291. add: function(name, value, pos) {
  6292. var list = this.list();
  6293. var start = this._positions.contentStart;
  6294. var styles = _.pick(this.options, 'styleBefore', 'styleSeparator');
  6295. var editTree = require('editTree');
  6296. if(_.isUndefined(pos)) pos = list.length;
  6297. /** @type CSSEditProperty */
  6298. var donor = list[pos];
  6299. if(donor) {
  6300. start = donor.fullRange().start;
  6301. } else if(donor = list[pos - 1]) {
  6302. // make sure that donor has terminating semicolon
  6303. donor.end(';');
  6304. start = donor.range().end;
  6305. }
  6306. if(donor) {
  6307. styles = _.pick(donor, 'styleBefore', 'styleSeparator');
  6308. }
  6309. var nameToken = editTree.createToken(start + styles.styleBefore.length, name);
  6310. var valueToken = editTree.createToken(nameToken.end + styles.styleSeparator.length, value);
  6311. var property = new CSSEditElement(this, nameToken, valueToken, editTree.createToken(valueToken.end, ';'));
  6312. _.extend(property, styles);
  6313. // write new property into the source
  6314. this._updateSource(property.styleBefore + property.toString(), start);
  6315. // insert new property
  6316. this._children.splice(pos, 0, property);
  6317. return property;
  6318. }
  6319. });
  6320. /**
  6321. * @class
  6322. * @type CSSEditElement
  6323. * @constructor
  6324. */
  6325. var CSSEditElement = require('editTree').EditElement.extend({
  6326. initialize: function(rule, name, value, end) {
  6327. this.styleBefore = rule.options.styleBefore;
  6328. this.styleSeparator = rule.options.styleSeparator;
  6329. this._end = end.value;
  6330. this._positions.end = end.start;
  6331. },
  6332. /**
  6333. * Returns ranges of complex value parts
  6334. * @returns {Array} Returns <code>null</code> if value is not complex
  6335. */
  6336. valueParts: function(isAbsolute) {
  6337. var parts = findParts(this.value());
  6338. if(isAbsolute) {
  6339. var offset = this.valuePosition(true);
  6340. _.each(parts, function(p) {
  6341. p.shift(offset);
  6342. });
  6343. }
  6344. return parts;
  6345. },
  6346. /**
  6347. * Sets of gets property end value (basically, it's a semicolon)
  6348. * @param {String} val New end value. If not passed, current
  6349. * value is returned
  6350. */
  6351. end: function(val) {
  6352. if(!_.isUndefined(val) && this._end !== val) {
  6353. this.parent._updateSource(val, this._positions.end, this._positions.end + this._end.length);
  6354. this._end = val;
  6355. }
  6356. return this._end;
  6357. },
  6358. /**
  6359. * Returns full rule range, with indentation
  6360. * @param {Boolean} isAbsolute Return absolute range (with respect of
  6361. * rule offset)
  6362. * @returns {Range}
  6363. */
  6364. fullRange: function(isAbsolute) {
  6365. var r = this.range(isAbsolute);
  6366. r.start -= this.styleBefore.length;
  6367. return r;
  6368. },
  6369. /**
  6370. * Returns item string representation
  6371. * @returns {String}
  6372. */
  6373. toString: function() {
  6374. return this.name() + this.styleSeparator + this.value() + this.end();
  6375. }
  6376. });
  6377. return {
  6378. /**
  6379. * Parses CSS rule into editable tree
  6380. * @param {String} source
  6381. * @param {Object} options
  6382. * @memberOf emmet.cssEditTree
  6383. * @returns {EditContainer}
  6384. */
  6385. parse: function(source, options) {
  6386. return new CSSEditContainer(source, options);
  6387. },
  6388. /**
  6389. * Extract and parse CSS rule from specified position in <code>content</code>
  6390. * @param {String} content CSS source code
  6391. * @param {Number} pos Character position where to start source code extraction
  6392. * @returns {EditContainer}
  6393. */
  6394. parseFromPosition: function(content, pos, isBackward) {
  6395. var bounds = this.extractRule(content, pos, isBackward);
  6396. if(!bounds || !bounds.inside(pos))
  6397. // no matching CSS rule or caret outside rule bounds
  6398. return null;
  6399. return this.parse(bounds.substring(content), {
  6400. offset: bounds.start
  6401. });
  6402. },
  6403. /**
  6404. * Extracts single CSS selector definition from source code
  6405. * @param {String} content CSS source code
  6406. * @param {Number} pos Character position where to start source code extraction
  6407. * @returns {Range}
  6408. */
  6409. extractRule: function(content, pos, isBackward) {
  6410. var result = '';
  6411. var len = content.length;
  6412. var offset = pos;
  6413. var stopChars = '{}/\\<>\n\r';
  6414. var bracePos = -1,
  6415. ch;
  6416. // search left until we find rule edge
  6417. while(offset >= 0) {
  6418. ch = content.charAt(offset);
  6419. if(ch == '{') {
  6420. bracePos = offset;
  6421. break;
  6422. } else if(ch == '}' && !isBackward) {
  6423. offset++;
  6424. break;
  6425. }
  6426. offset--;
  6427. }
  6428. // search right for full rule set
  6429. while(offset < len) {
  6430. ch = content.charAt(offset);
  6431. if(ch == '{') {
  6432. bracePos = offset;
  6433. } else if(ch == '}') {
  6434. if(bracePos != -1) result = content.substring(bracePos, offset + 1);
  6435. break;
  6436. }
  6437. offset++;
  6438. }
  6439. if(result) {
  6440. // find CSS selector
  6441. offset = bracePos - 1;
  6442. var selector = '';
  6443. while(offset >= 0) {
  6444. ch = content.charAt(offset);
  6445. if(stopChars.indexOf(ch) != -1) break;
  6446. offset--;
  6447. }
  6448. // also trim whitespace
  6449. selector = content.substring(offset + 1, bracePos).replace(/^[\s\n\r]+/m, '');
  6450. return require('range').create(bracePos - selector.length, result.length + selector.length);
  6451. }
  6452. return null;
  6453. },
  6454. /**
  6455. * Removes vendor prefix from CSS property
  6456. * @param {String} name CSS property
  6457. * @return {String}
  6458. */
  6459. baseName: function(name) {
  6460. return name.replace(/^\s*\-\w+\-/, '');
  6461. },
  6462. /**
  6463. * Finds parts of complex CSS value
  6464. * @param {String} str
  6465. * @returns {Array}
  6466. */
  6467. findParts: findParts
  6468. };
  6469. });
  6470. /**
  6471. * XML EditTree is a module that can parse an XML/HTML element into a tree with
  6472. * convenient methods for adding, modifying and removing attributes. These
  6473. * changes can be written back to string with respect of code formatting.
  6474. *
  6475. * @memberOf __xmlEditTreeDefine
  6476. * @constructor
  6477. * @param {Function} require
  6478. * @param {Underscore} _
  6479. */
  6480. emmet.define('xmlEditTree', function(require, _) {
  6481. var defaultOptions = {
  6482. styleBefore: ' ',
  6483. styleSeparator: '=',
  6484. styleQuote: '"',
  6485. offset: 0
  6486. };
  6487. var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/m;
  6488. var XMLEditContainer = require('editTree').EditContainer.extend({
  6489. initialize: function(source, options) {
  6490. _.defaults(this.options, defaultOptions);
  6491. this._positions.name = 1;
  6492. var attrToken = null;
  6493. var tokens = require('xmlParser').parse(source);
  6494. var range = require('range');
  6495. _.each(tokens, function(token) {
  6496. token.value = range.create(token).substring(source);
  6497. switch(token.type) {
  6498. case 'tag':
  6499. if(/^<[^\/]+/.test(token.value)) {
  6500. this._name = token.value.substring(1);
  6501. }
  6502. break;
  6503. case 'attribute':
  6504. // add empty attribute
  6505. if(attrToken) {
  6506. this._children.push(new XMLEditElement(this, attrToken));
  6507. }
  6508. attrToken = token;
  6509. break;
  6510. case 'string':
  6511. this._children.push(new XMLEditElement(this, attrToken, token));
  6512. attrToken = null;
  6513. break;
  6514. }
  6515. }, this);
  6516. if(attrToken) {
  6517. this._children.push(new XMLEditElement(this, attrToken));
  6518. }
  6519. this._saveStyle();
  6520. },
  6521. /**
  6522. * Remembers all styles of properties
  6523. * @private
  6524. */
  6525. _saveStyle: function() {
  6526. var start = this.nameRange().end;
  6527. var source = this.source;
  6528. _.each(this.list(), /** @param {EditElement} p */
  6529. function(p) {
  6530. p.styleBefore = source.substring(start, p.namePosition());
  6531. if(p.valuePosition() !== -1) {
  6532. p.styleSeparator = source.substring(p.namePosition() + p.name().length, p.valuePosition() - p.styleQuote.length);
  6533. }
  6534. start = p.range().end;
  6535. });
  6536. },
  6537. /**
  6538. * Adds new attribute
  6539. * @param {String} name Property name
  6540. * @param {String} value Property value
  6541. * @param {Number} pos Position at which to insert new property. By
  6542. * default the property is inserted at the end of rule
  6543. */
  6544. add: function(name, value, pos) {
  6545. var list = this.list();
  6546. var start = this.nameRange().end;
  6547. var editTree = require('editTree');
  6548. var styles = _.pick(this.options, 'styleBefore', 'styleSeparator', 'styleQuote');
  6549. if(_.isUndefined(pos)) pos = list.length;
  6550. /** @type XMLEditAttribute */
  6551. var donor = list[pos];
  6552. if(donor) {
  6553. start = donor.fullRange().start;
  6554. } else if(donor = list[pos - 1]) {
  6555. start = donor.range().end;
  6556. }
  6557. if(donor) {
  6558. styles = _.pick(donor, 'styleBefore', 'styleSeparator', 'styleQuote');
  6559. }
  6560. value = styles.styleQuote + value + styles.styleQuote;
  6561. var attribute = new XMLEditElement(this, editTree.createToken(start + styles.styleBefore.length, name), editTree.createToken(start + styles.styleBefore.length + name.length + styles.styleSeparator.length, value));
  6562. _.extend(attribute, styles);
  6563. // write new attribute into the source
  6564. this._updateSource(attribute.styleBefore + attribute.toString(), start);
  6565. // insert new attribute
  6566. this._children.splice(pos, 0, attribute);
  6567. return attribute;
  6568. }
  6569. });
  6570. var XMLEditElement = require('editTree').EditElement.extend({
  6571. initialize: function(parent, nameToken, valueToken) {
  6572. this.styleBefore = parent.options.styleBefore;
  6573. this.styleSeparator = parent.options.styleSeparator;
  6574. var value = '',
  6575. quote = parent.options.styleQuote;
  6576. if(valueToken) {
  6577. value = valueToken.value;
  6578. quote = value.charAt(0);
  6579. if(quote == '"' || quote == "'") {
  6580. value = value.substring(1);
  6581. } else {
  6582. quote = '';
  6583. }
  6584. if(quote && value.charAt(value.length - 1) == quote) {
  6585. value = value.substring(0, value.length - 1);
  6586. }
  6587. }
  6588. this.styleQuote = quote;
  6589. this._value = value;
  6590. this._positions.value = valueToken ? valueToken.start + quote.length : -1;
  6591. },
  6592. /**
  6593. * Returns full rule range, with indentation
  6594. * @param {Boolean} isAbsolute Return absolute range (with respect of
  6595. * rule offset)
  6596. * @returns {Range}
  6597. */
  6598. fullRange: function(isAbsolute) {
  6599. var r = this.range(isAbsolute);
  6600. r.start -= this.styleBefore.length;
  6601. return r;
  6602. },
  6603. toString: function() {
  6604. return this.name() + this.styleSeparator + this.styleQuote + this.value() + this.styleQuote;
  6605. }
  6606. });
  6607. return {
  6608. /**
  6609. * Parses HTML element into editable tree
  6610. * @param {String} source
  6611. * @param {Object} options
  6612. * @memberOf emmet.htmlEditTree
  6613. * @returns {EditContainer}
  6614. */
  6615. parse: function(source, options) {
  6616. return new XMLEditContainer(source, options);
  6617. },
  6618. /**
  6619. * Extract and parse HTML from specified position in <code>content</code>
  6620. * @param {String} content CSS source code
  6621. * @param {Number} pos Character position where to start source code extraction
  6622. * @returns {XMLEditElement}
  6623. */
  6624. parseFromPosition: function(content, pos, isBackward) {
  6625. var bounds = this.extractTag(content, pos, isBackward);
  6626. if(!bounds || !bounds.inside(pos))
  6627. // no matching HTML tag or caret outside tag bounds
  6628. return null;
  6629. return this.parse(bounds.substring(content), {
  6630. offset: bounds.start
  6631. });
  6632. },
  6633. /**
  6634. * Extracts nearest HTML tag range from <code>content</code>, starting at
  6635. * <code>pos</code> position
  6636. * @param {String} content
  6637. * @param {Number} pos
  6638. * @param {Boolean} isBackward
  6639. * @returns {Range}
  6640. */
  6641. extractTag: function(content, pos, isBackward) {
  6642. var len = content.length,
  6643. i;
  6644. var range = require('range');
  6645. // max extraction length. I don't think there may be tags larger
  6646. // than 2000 characters length
  6647. var maxLen = Math.min(2000, len);
  6648. /** @type Range */
  6649. var r = null;
  6650. var match = function(pos) {
  6651. var m;
  6652. if(content.charAt(pos) == '<' && (m = content.substr(pos, maxLen).match(startTag))) return range.create(pos, m[0]);
  6653. };
  6654. // lookup backward, in case we are inside tag already
  6655. for(i = pos; i >= 0; i--) {
  6656. if(r = match(i)) break;
  6657. }
  6658. if(r && (r.inside(pos) || isBackward)) return r;
  6659. if(!r && isBackward) return null;
  6660. // search forward
  6661. for(i = pos; i < len; i++) {
  6662. if(r = match(i)) return r;
  6663. }
  6664. }
  6665. };
  6666. });
  6667. /**
  6668. * 'Expand abbreviation' editor action: extracts abbreviation from current caret
  6669. * position and replaces it with formatted output.
  6670. * <br><br>
  6671. * This behavior can be overridden with custom handlers which can perform
  6672. * different actions when 'Expand Abbreviation' action is called.
  6673. * For example, a CSS gradient handler that produces vendor-prefixed gradient
  6674. * definitions registers its own expand abbreviation handler.
  6675. *
  6676. * @constructor
  6677. * @memberOf __expandAbbreviationActionDefine
  6678. * @param {Function} require
  6679. * @param {Underscore} _
  6680. */
  6681. emmet.define('expandAbbreviation', function(require, _) {
  6682. /**
  6683. * @type HandlerList List of registered handlers
  6684. */
  6685. var handlers = require('handlerList').create();
  6686. /** Back-reference to module */
  6687. var module = null;
  6688. var actions = require('actions');
  6689. /**
  6690. * 'Expand abbreviation' editor action
  6691. * @param {IEmmetEditor} editor Editor instance
  6692. * @param {String} syntax Syntax type (html, css, etc.)
  6693. * @param {String} profile Output profile name (html, xml, xhtml)
  6694. * @return {Boolean} Returns <code>true</code> if abbreviation was expanded
  6695. * successfully
  6696. */
  6697. actions.add('expand_abbreviation', function(editor, syntax, profile) {
  6698. var args = _.toArray(arguments);
  6699. // normalize incoming arguments
  6700. var info = require('editorUtils').outputInfo(editor, syntax, profile);
  6701. args[1] = info.syntax;
  6702. args[2] = info.profile;
  6703. return handlers.exec(false, args);
  6704. });
  6705. /**
  6706. * A special version of <code>expandAbbreviation</code> function: if it can't
  6707. * find abbreviation, it will place Tab character at caret position
  6708. * @param {IEmmetEditor} editor Editor instance
  6709. * @param {String} syntax Syntax type (html, css, etc.)
  6710. * @param {String} profile Output profile name (html, xml, xhtml)
  6711. */
  6712. actions.add('expand_abbreviation_with_tab', function(editor, syntax, profile) {
  6713. if(!actions.run('expand_abbreviation', editor, syntax, profile)) editor.replaceContent(require('resources').getVariable('indentation'), editor.getCaretPos());
  6714. }, {
  6715. hidden: true
  6716. });
  6717. // XXX setup default handler
  6718. /**
  6719. * Extracts abbreviation from current caret
  6720. * position and replaces it with formatted output
  6721. * @param {IEmmetEditor} editor Editor instance
  6722. * @param {String} syntax Syntax type (html, css, etc.)
  6723. * @param {String} profile Output profile name (html, xml, xhtml)
  6724. * @return {Boolean} Returns <code>true</code> if abbreviation was expanded
  6725. * successfully
  6726. */
  6727. handlers.add(function(editor, syntax, profile) {
  6728. var caretPos = editor.getSelectionRange().end;
  6729. var abbr = module.findAbbreviation(editor);
  6730. if(abbr) {
  6731. var content = emmet.expandAbbreviation(abbr, syntax, profile, require('actionUtils').captureContext(editor));
  6732. if(content) {
  6733. editor.replaceContent(content, caretPos - abbr.length, caretPos);
  6734. return true;
  6735. }
  6736. }
  6737. return false;
  6738. }, {
  6739. order: -1
  6740. });
  6741. return module = {
  6742. /**
  6743. * Adds custom expand abbreviation handler. The passed function should
  6744. * return <code>true</code> if it was performed successfully,
  6745. * <code>false</code> otherwise.
  6746. *
  6747. * Added handlers will be called when 'Expand Abbreviation' is called
  6748. * in order they were added
  6749. * @memberOf expandAbbreviation
  6750. * @param {Function} fn
  6751. * @param {Object} options
  6752. */
  6753. addHandler: function(fn, options) {
  6754. handlers.add(fn, options);
  6755. },
  6756. /**
  6757. * Removes registered handler
  6758. * @returns
  6759. */
  6760. removeHandler: function(fn) {
  6761. handlers.remove(fn, options);
  6762. },
  6763. /**
  6764. * Search for abbreviation in editor from current caret position
  6765. * @param {IEmmetEditor} editor Editor instance
  6766. * @return {String}
  6767. */
  6768. findAbbreviation: function(editor) { /** @type Range */
  6769. var range = require('range').create(editor.getSelectionRange());
  6770. var content = String(editor.getContent());
  6771. if(range.length()) {
  6772. // abbreviation is selected by user
  6773. return range.substring(content);
  6774. }
  6775. // search for new abbreviation from current caret position
  6776. var curLine = editor.getCurrentLineRange();
  6777. return require('actionUtils').extractAbbreviation(content.substring(curLine.start, range.start));
  6778. }
  6779. };
  6780. });
  6781. /**
  6782. * Action that wraps content with abbreviation. For convenience, action is
  6783. * defined as reusable module
  6784. * @constructor
  6785. * @memberOf __wrapWithAbbreviationDefine
  6786. */
  6787. emmet.define('wrapWithAbbreviation', function(require, _) { /** Back-references to current module */
  6788. var module = null;
  6789. /**
  6790. * Wraps content with abbreviation
  6791. * @param {IEmmetEditor} Editor instance
  6792. * @param {String} abbr Abbreviation to wrap with
  6793. * @param {String} syntax Syntax type (html, css, etc.)
  6794. * @param {String} profile Output profile name (html, xml, xhtml)
  6795. */
  6796. require('actions').add('wrap_with_abbreviation', function(editor, abbr, syntax, profile) {
  6797. var info = require('editorUtils').outputInfo(editor, syntax, profile);
  6798. var utils = require('utils'); /** @type emmet.editorUtils */
  6799. var editorUtils = require('editorUtils');
  6800. var matcher = require('html_matcher');
  6801. abbr = abbr || editor.prompt("Enter abbreviation");
  6802. if(!abbr) return null;
  6803. abbr = String(abbr);
  6804. var range = editor.getSelectionRange();
  6805. var startOffset = range.start;
  6806. var endOffset = range.end;
  6807. if(startOffset == endOffset) {
  6808. // no selection, find tag pair
  6809. range = matcher(info.content, startOffset, info.profile);
  6810. if(!range || range[0] == -1) // nothing to wrap
  6811. return false;
  6812. /** @type Range */
  6813. var narrowedSel = utils.narrowToNonSpace(info.content, range[0], range[1] - range[0]);
  6814. startOffset = narrowedSel.start;
  6815. endOffset = narrowedSel.end;
  6816. }
  6817. var newContent = utils.escapeText(info.content.substring(startOffset, endOffset));
  6818. var result = module.wrap(abbr, editorUtils.unindent(editor, newContent), info.syntax, info.profile, require('actionUtils').captureContext(editor));
  6819. if(result) {
  6820. editor.replaceContent(result, startOffset, endOffset);
  6821. return true;
  6822. }
  6823. return false;
  6824. });
  6825. return module = {
  6826. /**
  6827. * Wraps passed text with abbreviation. Text will be placed inside last
  6828. * expanded element
  6829. * @memberOf wrapWithAbbreviation
  6830. * @param {String} abbr Abbreviation
  6831. * @param {String} text Text to wrap
  6832. * @param {String} syntax Document type (html, xml, etc.). Default is 'html'
  6833. * @param {String} profile Output profile's name. Default is 'plain'
  6834. * @param {Object} contextNode Context node inside which abbreviation
  6835. * is wrapped. It will be used as a reference for node name resolvers
  6836. * @return {String}
  6837. */
  6838. wrap: function(abbr, text, syntax, profile, contextNode) { /** @type emmet.filters */
  6839. var filters = require('filters'); /** @type emmet.utils */
  6840. var utils = require('utils');
  6841. syntax = syntax || emmet.defaultSyntax();
  6842. profile = profile || emmet.defaultProfile();
  6843. require('tabStops').resetTabstopIndex();
  6844. var data = filters.extractFromAbbreviation(abbr);
  6845. var parsedTree = require('abbreviationParser').parse(data[0], {
  6846. syntax: syntax,
  6847. pastedContent: text,
  6848. contextNode: contextNode
  6849. });
  6850. if(parsedTree) {
  6851. var filtersList = filters.composeList(syntax, profile, data[1]);
  6852. filters.apply(parsedTree, filtersList, profile);
  6853. return utils.replaceVariables(parsedTree.toString());
  6854. }
  6855. return null;
  6856. }
  6857. };
  6858. });
  6859. /**
  6860. * Toggles HTML and CSS comments depending on current caret context. Unlike
  6861. * the same action in most editors, this action toggles comment on currently
  6862. * matched item—HTML tag or CSS selector—when nothing is selected.
  6863. *
  6864. * @param {Function} require
  6865. * @param {Underscore} _
  6866. * @memberOf __toggleCommentAction
  6867. * @constructor
  6868. */
  6869. emmet.exec(function(require, _) {
  6870. /**
  6871. * Toggle HTML comment on current selection or tag
  6872. * @param {IEmmetEditor} editor
  6873. * @return {Boolean} Returns <code>true</code> if comment was toggled
  6874. */
  6875. function toggleHTMLComment(editor) { /** @type Range */
  6876. var range = require('range').create(editor.getSelectionRange());
  6877. var info = require('editorUtils').outputInfo(editor);
  6878. if(!range.length()) {
  6879. // no selection, find matching tag
  6880. var pair = require('html_matcher').getTags(info.content, editor.getCaretPos(), info.profile);
  6881. if(pair && pair[0]) { // found pair
  6882. range.start = pair[0].start;
  6883. range.end = pair[1] ? pair[1].end : pair[0].end;
  6884. }
  6885. }
  6886. return genericCommentToggle(editor, '<!--', '-->', range);
  6887. }
  6888. /**
  6889. * Simple CSS commenting
  6890. * @param {IEmmetEditor} editor
  6891. * @return {Boolean} Returns <code>true</code> if comment was toggled
  6892. */
  6893. function toggleCSSComment(editor) { /** @type Range */
  6894. var range = require('range').create(editor.getSelectionRange());
  6895. var info = require('editorUtils').outputInfo(editor);
  6896. if(!range.length()) {
  6897. // no selection, try to get current rule
  6898. /** @type CSSRule */
  6899. var rule = require('cssEditTree').parseFromPosition(info.content, editor.getCaretPos());
  6900. if(rule) {
  6901. var property = cssItemFromPosition(rule, editor.getCaretPos());
  6902. range = property ? property.range(true) : require('range').create(rule.nameRange(true).start, rule.source);
  6903. }
  6904. }
  6905. if(!range.length()) {
  6906. // still no selection, get current line
  6907. range = require('range').create(editor.getCurrentLineRange());
  6908. require('utils').narrowToNonSpace(info.content, range);
  6909. }
  6910. return genericCommentToggle(editor, '/*', '*/', range);
  6911. }
  6912. /**
  6913. * Returns CSS property from <code>rule</code> that matches passed position
  6914. * @param {EditContainer} rule
  6915. * @param {Number} absPos
  6916. * @returns {EditElement}
  6917. */
  6918. function cssItemFromPosition(rule, absPos) {
  6919. // do not use default EditContainer.itemFromPosition() here, because
  6920. // we need to make a few assumptions to make CSS commenting more reliable
  6921. var relPos = absPos - (rule.options.offset || 0);
  6922. var reSafeChar = /^[\s\n\r]/;
  6923. return _.find(rule.list(), function(item) {
  6924. if(item.range().end === relPos) {
  6925. // at the end of property, but outside of it
  6926. // if there’s a space character at current position,
  6927. // use current property
  6928. return reSafeChar.test(rule.source.charAt(relPos));
  6929. }
  6930. return item.range().inside(relPos);
  6931. });
  6932. }
  6933. /**
  6934. * Search for nearest comment in <code>str</code>, starting from index <code>from</code>
  6935. * @param {String} text Where to search
  6936. * @param {Number} from Search start index
  6937. * @param {String} start_token Comment start string
  6938. * @param {String} end_token Comment end string
  6939. * @return {Range} Returns null if comment wasn't found
  6940. */
  6941. function searchComment(text, from, startToken, endToken) {
  6942. var commentStart = -1;
  6943. var commentEnd = -1;
  6944. var hasMatch = function(str, start) {
  6945. return text.substr(start, str.length) == str;
  6946. };
  6947. // search for comment start
  6948. while(from--) {
  6949. if(hasMatch(startToken, from)) {
  6950. commentStart = from;
  6951. break;
  6952. }
  6953. }
  6954. if(commentStart != -1) {
  6955. // search for comment end
  6956. from = commentStart;
  6957. var contentLen = text.length;
  6958. while(contentLen >= from++) {
  6959. if(hasMatch(endToken, from)) {
  6960. commentEnd = from + endToken.length;
  6961. break;
  6962. }
  6963. }
  6964. }
  6965. return(commentStart != -1 && commentEnd != -1) ? require('range').create(commentStart, commentEnd - commentStart) : null;
  6966. }
  6967. /**
  6968. * Generic comment toggling routine
  6969. * @param {IEmmetEditor} editor
  6970. * @param {String} commentStart Comment start token
  6971. * @param {String} commentEnd Comment end token
  6972. * @param {Range} range Selection range
  6973. * @return {Boolean}
  6974. */
  6975. function genericCommentToggle(editor, commentStart, commentEnd, range) {
  6976. var editorUtils = require('editorUtils');
  6977. var content = editorUtils.outputInfo(editor).content;
  6978. var caretPos = editor.getCaretPos();
  6979. var newContent = null;
  6980. var utils = require('utils');
  6981. /**
  6982. * Remove comment markers from string
  6983. * @param {Sting} str
  6984. * @return {String}
  6985. */
  6986. function removeComment(str) {
  6987. return str.replace(new RegExp('^' + utils.escapeForRegexp(commentStart) + '\\s*'), function(str) {
  6988. caretPos -= str.length;
  6989. return '';
  6990. }).replace(new RegExp('\\s*' + utils.escapeForRegexp(commentEnd) + '$'), '');
  6991. }
  6992. // first, we need to make sure that this substring is not inside
  6993. // comment
  6994. var commentRange = searchComment(content, caretPos, commentStart, commentEnd);
  6995. if(commentRange && commentRange.overlap(range)) {
  6996. // we're inside comment, remove it
  6997. range = commentRange;
  6998. newContent = removeComment(range.substring(content));
  6999. } else {
  7000. // should add comment
  7001. // make sure that there's no comment inside selection
  7002. newContent = commentStart + ' ' + range.substring(content).replace(new RegExp(utils.escapeForRegexp(commentStart) + '\\s*|\\s*' + utils.escapeForRegexp(commentEnd), 'g'), '') + ' ' + commentEnd;
  7003. // adjust caret position
  7004. caretPos += commentStart.length + 1;
  7005. }
  7006. // replace editor content
  7007. if(newContent !== null) {
  7008. newContent = utils.escapeText(newContent);
  7009. editor.setCaretPos(range.start);
  7010. editor.replaceContent(editorUtils.unindent(editor, newContent), range.start, range.end);
  7011. editor.setCaretPos(caretPos);
  7012. return true;
  7013. }
  7014. return false;
  7015. }
  7016. /**
  7017. * Toggle comment on current editor's selection or HTML tag/CSS rule
  7018. * @param {IEmmetEditor} editor
  7019. */
  7020. require('actions').add('toggle_comment', function(editor) {
  7021. var info = require('editorUtils').outputInfo(editor);
  7022. if(info.syntax == 'css') {
  7023. // in case our editor is good enough and can recognize syntax from
  7024. // current token, we have to make sure that cursor is not inside
  7025. // 'style' attribute of html element
  7026. var caretPos = editor.getCaretPos();
  7027. var pair = require('html_matcher').getTags(info.content, caretPos);
  7028. if(pair && pair[0] && pair[0].type == 'tag' && pair[0].start <= caretPos && pair[0].end >= caretPos) {
  7029. info.syntax = 'html';
  7030. }
  7031. }
  7032. if(info.syntax == 'css') return toggleCSSComment(editor);
  7033. return toggleHTMLComment(editor);
  7034. });
  7035. });
  7036. /**
  7037. * Move between next/prev edit points. 'Edit points' are places between tags
  7038. * and quotes of empty attributes in html
  7039. * @constructor
  7040. *
  7041. * @memberOf __editPointActionDefine
  7042. * @param {Function} require
  7043. * @param {Underscore} _
  7044. */
  7045. emmet.exec(function(require, _) {
  7046. /**
  7047. * Search for new caret insertion point
  7048. * @param {IEmmetEditor} editor Editor instance
  7049. * @param {Number} inc Search increment: -1 — search left, 1 — search right
  7050. * @param {Number} offset Initial offset relative to current caret position
  7051. * @return {Number} Returns -1 if insertion point wasn't found
  7052. */
  7053. function findNewEditPoint(editor, inc, offset) {
  7054. inc = inc || 1;
  7055. offset = offset || 0;
  7056. var curPoint = editor.getCaretPos() + offset;
  7057. var content = String(editor.getContent());
  7058. var maxLen = content.length;
  7059. var nextPoint = -1;
  7060. var reEmptyLine = /^\s+$/;
  7061. function getLine(ix) {
  7062. var start = ix;
  7063. while(start >= 0) {
  7064. var c = content.charAt(start);
  7065. if(c == '\n' || c == '\r') break;
  7066. start--;
  7067. }
  7068. return content.substring(start, ix);
  7069. }
  7070. while(curPoint <= maxLen && curPoint >= 0) {
  7071. curPoint += inc;
  7072. var curChar = content.charAt(curPoint);
  7073. var nextChar = content.charAt(curPoint + 1);
  7074. var prevChar = content.charAt(curPoint - 1);
  7075. switch(curChar) {
  7076. case '"':
  7077. case '\'':
  7078. if(nextChar == curChar && prevChar == '=') {
  7079. // empty attribute
  7080. nextPoint = curPoint + 1;
  7081. }
  7082. break;
  7083. case '>':
  7084. if(nextChar == '<') {
  7085. // between tags
  7086. nextPoint = curPoint + 1;
  7087. }
  7088. break;
  7089. case '\n':
  7090. case '\r':
  7091. // empty line
  7092. if(reEmptyLine.test(getLine(curPoint - 1))) {
  7093. nextPoint = curPoint;
  7094. }
  7095. break;
  7096. }
  7097. if(nextPoint != -1) break;
  7098. }
  7099. return nextPoint;
  7100. }
  7101. /** @type emmet.actions */
  7102. var actions = require('actions');
  7103. /**
  7104. * Move caret to previous edit point
  7105. * @param {IEmmetEditor} editor Editor instance
  7106. */
  7107. actions.add('prev_edit_point', function(editor) {
  7108. var curPos = editor.getCaretPos();
  7109. var newPoint = findNewEditPoint(editor, -1);
  7110. if(newPoint == curPos)
  7111. // we're still in the same point, try searching from the other place
  7112. newPoint = findNewEditPoint(editor, -1, -2);
  7113. if(newPoint != -1) {
  7114. editor.setCaretPos(newPoint);
  7115. return true;
  7116. }
  7117. return false;
  7118. }, {
  7119. label: 'Previous Edit Point'
  7120. });
  7121. /**
  7122. * Move caret to next edit point
  7123. * @param {IEmmetEditor} editor Editor instance
  7124. */
  7125. actions.add('next_edit_point', function(editor) {
  7126. var newPoint = findNewEditPoint(editor, 1);
  7127. if(newPoint != -1) editor.setCaretPos(newPoint);
  7128. });
  7129. });
  7130. /**
  7131. * Actions that use stream parsers and tokenizers for traversing:
  7132. * -- Search for next/previous items in HTML
  7133. * -- Search for next/previous items in CSS
  7134. * @constructor
  7135. * @memberOf __selectItemActionDefine
  7136. * @param {Function} require
  7137. * @param {Underscore} _
  7138. */
  7139. emmet.exec(function(require, _) {
  7140. var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
  7141. /**
  7142. * Generic function for searching for items to select
  7143. * @param {IEmmetEditor} editor
  7144. * @param {Boolean} isBackward Search backward (search forward otherwise)
  7145. * @param {Function} extractFn Function that extracts item content
  7146. * @param {Function} rangeFn Function that search for next token range
  7147. */
  7148. function findItem(editor, isBackward, extractFn, rangeFn) {
  7149. var range = require('range');
  7150. var content = require('editorUtils').outputInfo(editor).content;
  7151. var contentLength = content.length;
  7152. var itemRange, rng; /** @type Range */
  7153. var prevRange = range.create(-1, 0); /** @type Range */
  7154. var sel = range.create(editor.getSelectionRange());
  7155. var searchPos = sel.start,
  7156. loop = 100000; // endless loop protection
  7157. while(searchPos >= 0 && searchPos < contentLength && --loop > 0) {
  7158. if((itemRange = extractFn(content, searchPos, isBackward))) {
  7159. if(prevRange.equal(itemRange)) {
  7160. break;
  7161. }
  7162. prevRange = itemRange.clone();
  7163. rng = rangeFn(itemRange.substring(content), itemRange.start, sel.clone());
  7164. if(rng) {
  7165. editor.createSelection(rng.start, rng.end);
  7166. return true;
  7167. } else {
  7168. searchPos = isBackward ? itemRange.start : itemRange.end - 1;
  7169. }
  7170. }
  7171. searchPos += isBackward ? -1 : 1;
  7172. }
  7173. return false;
  7174. }
  7175. // XXX HTML section
  7176. /**
  7177. * Find next HTML item
  7178. * @param {IEmmetEditor} editor
  7179. */
  7180. function findNextHTMLItem(editor) {
  7181. var isFirst = true;
  7182. return findItem(editor, false, function(content, searchPos) {
  7183. if(isFirst) {
  7184. isFirst = false;
  7185. return findOpeningTagFromPosition(content, searchPos);
  7186. } else {
  7187. return getOpeningTagFromPosition(content, searchPos);
  7188. }
  7189. }, function(tag, offset, selRange) {
  7190. return getRangeForHTMLItem(tag, offset, selRange, false);
  7191. });
  7192. }
  7193. /**
  7194. * Find previous HTML item
  7195. * @param {IEmmetEditor} editor
  7196. */
  7197. function findPrevHTMLItem(editor) {
  7198. return findItem(editor, true, getOpeningTagFromPosition, function(tag, offset, selRange) {
  7199. return getRangeForHTMLItem(tag, offset, selRange, true);
  7200. });
  7201. }
  7202. /**
  7203. * Creates possible selection ranges for HTML tag
  7204. * @param {String} source Original HTML source for tokens
  7205. * @param {Array} tokens List of HTML tokens
  7206. * @returns {Array}
  7207. */
  7208. function makePossibleRangesHTML(source, tokens, offset) {
  7209. offset = offset || 0;
  7210. var range = require('range');
  7211. var result = [];
  7212. var attrStart = -1,
  7213. attrName = '',
  7214. attrValue = '',
  7215. attrValueRange, tagName;
  7216. _.each(tokens, function(tok) {
  7217. switch(tok.type) {
  7218. case 'tag':
  7219. tagName = source.substring(tok.start, tok.end);
  7220. if(/^<[\w\:\-]/.test(tagName)) {
  7221. // add tag name
  7222. result.push(range.create({
  7223. start: tok.start + 1,
  7224. end: tok.end
  7225. }));
  7226. }
  7227. break;
  7228. case 'attribute':
  7229. attrStart = tok.start;
  7230. attrName = source.substring(tok.start, tok.end);
  7231. break;
  7232. case 'string':
  7233. // attribute value
  7234. // push full attribute first
  7235. result.push(range.create(attrStart, tok.end - attrStart));
  7236. attrValueRange = range.create(tok);
  7237. attrValue = attrValueRange.substring(source);
  7238. // is this a quoted attribute?
  7239. if(isQuote(attrValue.charAt(0))) attrValueRange.start++;
  7240. if(isQuote(attrValue.charAt(attrValue.length - 1))) attrValueRange.end--;
  7241. result.push(attrValueRange);
  7242. if(attrName == 'class') {
  7243. result = result.concat(classNameRanges(attrValueRange.substring(source), attrValueRange.start));
  7244. }
  7245. break;
  7246. }
  7247. });
  7248. // offset ranges
  7249. _.each(result, function(r) {
  7250. r.shift(offset);
  7251. });
  7252. return _.chain(result).filter(function(item) { // remove empty
  7253. return !!item.length();
  7254. }).uniq(false, function(item) { // remove duplicates
  7255. return item.toString();
  7256. }).value();
  7257. }
  7258. /**
  7259. * Returns ranges of class names in "class" attribute value
  7260. * @param {String} className
  7261. * @returns {Array}
  7262. */
  7263. function classNameRanges(className, offset) {
  7264. offset = offset || 0;
  7265. var result = []; /** @type StringStream */
  7266. var stream = require('stringStream').create(className);
  7267. var range = require('range');
  7268. // skip whitespace
  7269. stream.eatSpace();
  7270. stream.start = stream.pos;
  7271. var ch;
  7272. while(ch = stream.next()) {
  7273. if(/[\s\u00a0]/.test(ch)) {
  7274. result.push(range.create(stream.start + offset, stream.pos - stream.start - 1));
  7275. stream.eatSpace();
  7276. stream.start = stream.pos;
  7277. }
  7278. }
  7279. result.push(range.create(stream.start + offset, stream.pos - stream.start));
  7280. return result;
  7281. }
  7282. /**
  7283. * Returns best HTML tag range match for current selection
  7284. * @param {String} tag Tag declaration
  7285. * @param {Number} offset Tag's position index inside content
  7286. * @param {Range} selRange Selection range
  7287. * @return {Range} Returns range if next item was found, <code>null</code> otherwise
  7288. */
  7289. function getRangeForHTMLItem(tag, offset, selRange, isBackward) {
  7290. var ranges = makePossibleRangesHTML(tag, require('xmlParser').parse(tag), offset);
  7291. if(isBackward) ranges.reverse();
  7292. // try to find selected range
  7293. var curRange = _.find(ranges, function(r) {
  7294. return r.equal(selRange);
  7295. });
  7296. if(curRange) {
  7297. var ix = _.indexOf(ranges, curRange);
  7298. if(ix < ranges.length - 1) return ranges[ix + 1];
  7299. return null;
  7300. }
  7301. // no selected range, find nearest one
  7302. if(isBackward)
  7303. // search backward
  7304. return _.find(ranges, function(r) {
  7305. return r.start < selRange.start;
  7306. });
  7307. // search forward
  7308. // to deal with overlapping ranges (like full attribute definition
  7309. // and attribute value) let's find range under caret first
  7310. if(!curRange) {
  7311. var matchedRanges = _.filter(ranges, function(r) {
  7312. return r.inside(selRange.end);
  7313. });
  7314. if(matchedRanges.length > 1) return matchedRanges[1];
  7315. }
  7316. return _.find(ranges, function(r) {
  7317. return r.end > selRange.end;
  7318. });
  7319. }
  7320. /**
  7321. * Search for opening tag in content, starting at specified position
  7322. * @param {String} html Where to search tag
  7323. * @param {Number} pos Character index where to start searching
  7324. * @return {Range} Returns range if valid opening tag was found,
  7325. * <code>null</code> otherwise
  7326. */
  7327. function findOpeningTagFromPosition(html, pos) {
  7328. var tag;
  7329. while(pos >= 0) {
  7330. if(tag = getOpeningTagFromPosition(html, pos)) return tag;
  7331. pos--;
  7332. }
  7333. return null;
  7334. }
  7335. /**
  7336. * @param {String} html Where to search tag
  7337. * @param {Number} pos Character index where to start searching
  7338. * @return {Range} Returns range if valid opening tag was found,
  7339. * <code>null</code> otherwise
  7340. */
  7341. function getOpeningTagFromPosition(html, pos) {
  7342. var m;
  7343. if(html.charAt(pos) == '<' && (m = html.substring(pos, html.length).match(startTag))) {
  7344. return require('range').create(pos, m[0]);
  7345. }
  7346. }
  7347. function isQuote(ch) {
  7348. return ch == '"' || ch == "'";
  7349. }
  7350. /**
  7351. * Makes all possible selection ranges for specified CSS property
  7352. * @param {CSSProperty} property
  7353. * @returns {Array}
  7354. */
  7355. function makePossibleRangesCSS(property) {
  7356. // find all possible ranges, sorted by position and size
  7357. var valueRange = property.valueRange(true);
  7358. var result = [property.range(true), valueRange];
  7359. var stringStream = require('stringStream');
  7360. var cssEditTree = require('cssEditTree');
  7361. var range = require('range');
  7362. // locate parts of complex values.
  7363. // some examples:
  7364. // – 1px solid red: 3 parts
  7365. // – arial, sans-serif: enumeration, 2 parts
  7366. // – url(image.png): function value part
  7367. var value = property.value();
  7368. _.each(property.valueParts(), function(r) {
  7369. // add absolute range
  7370. var clone = r.clone();
  7371. result.push(clone.shift(valueRange.start));
  7372. /** @type StringStream */
  7373. var stream = stringStream.create(r.substring(value));
  7374. if(stream.match(/^[\w\-]+\(/, true)) {
  7375. // we have a function, find values in it.
  7376. // but first add function contents
  7377. stream.start = stream.pos;
  7378. stream.skipToPair('(', ')');
  7379. var fnBody = stream.current();
  7380. result.push(range.create(clone.start + stream.start, fnBody));
  7381. // find parts
  7382. _.each(cssEditTree.findParts(fnBody), function(part) {
  7383. result.push(range.create(clone.start + stream.start + part.start, part.substring(fnBody)));
  7384. });
  7385. }
  7386. });
  7387. // optimize result: remove empty ranges and duplicates
  7388. return _.chain(result).filter(function(item) {
  7389. return !!item.length();
  7390. }).uniq(false, function(item) {
  7391. return item.toString();
  7392. }).value();
  7393. }
  7394. /**
  7395. * Tries to find matched CSS property and nearest range for selection
  7396. * @param {CSSRule} rule
  7397. * @param {Range} selRange
  7398. * @param {Boolean} isBackward
  7399. * @returns {Range}
  7400. */
  7401. function matchedRangeForCSSProperty(rule, selRange, isBackward) { /** @type CSSProperty */
  7402. var property = null;
  7403. var possibleRanges, curRange = null,
  7404. ix;
  7405. var list = rule.list();
  7406. var searchFn, nearestItemFn;
  7407. if(isBackward) {
  7408. list.reverse();
  7409. searchFn = function(p) {
  7410. return p.range(true).start <= selRange.start;
  7411. };
  7412. nearestItemFn = function(r) {
  7413. return r.start < selRange.start;
  7414. };
  7415. } else {
  7416. searchFn = function(p) {
  7417. return p.range(true).end >= selRange.end;
  7418. };
  7419. nearestItemFn = function(r) {
  7420. return r.end > selRange.start;
  7421. };
  7422. }
  7423. // search for nearest to selection CSS property
  7424. while(property = _.find(list, searchFn)) {
  7425. possibleRanges = makePossibleRangesCSS(property);
  7426. if(isBackward) possibleRanges.reverse();
  7427. // check if any possible range is already selected
  7428. curRange = _.find(possibleRanges, function(r) {
  7429. return r.equal(selRange);
  7430. });
  7431. if(!curRange) {
  7432. // no selection, select nearest item
  7433. var matchedRanges = _.filter(possibleRanges, function(r) {
  7434. return r.inside(selRange.end);
  7435. });
  7436. if(matchedRanges.length > 1) {
  7437. curRange = matchedRanges[1];
  7438. break;
  7439. }
  7440. if(curRange = _.find(possibleRanges, nearestItemFn)) break;
  7441. } else {
  7442. ix = _.indexOf(possibleRanges, curRange);
  7443. if(ix != possibleRanges.length - 1) {
  7444. curRange = possibleRanges[ix + 1];
  7445. break;
  7446. }
  7447. }
  7448. curRange = null;
  7449. selRange.start = selRange.end = isBackward ? property.range(true).start - 1 : property.range(true).end + 1;
  7450. }
  7451. return curRange;
  7452. }
  7453. function findNextCSSItem(editor) {
  7454. return findItem(editor, false, require('cssEditTree').extractRule, getRangeForNextItemInCSS);
  7455. }
  7456. function findPrevCSSItem(editor) {
  7457. return findItem(editor, true, require('cssEditTree').extractRule, getRangeForPrevItemInCSS);
  7458. }
  7459. /**
  7460. * Returns range for item to be selected in CSS after current caret
  7461. * (selection) position
  7462. * @param {String} rule CSS rule declaration
  7463. * @param {Number} offset Rule's position index inside content
  7464. * @param {Range} selRange Selection range
  7465. * @return {Range} Returns range if next item was found, <code>null</code> otherwise
  7466. */
  7467. function getRangeForNextItemInCSS(rule, offset, selRange) {
  7468. var tree = require('cssEditTree').parse(rule, {
  7469. offset: offset
  7470. });
  7471. // check if selector is matched
  7472. var range = tree.nameRange(true);
  7473. if(selRange.end < range.end) {
  7474. return range;
  7475. }
  7476. return matchedRangeForCSSProperty(tree, selRange, false);
  7477. }
  7478. /**
  7479. * Returns range for item to be selected in CSS before current caret
  7480. * (selection) position
  7481. * @param {String} rule CSS rule declaration
  7482. * @param {Number} offset Rule's position index inside content
  7483. * @param {Range} selRange Selection range
  7484. * @return {Range} Returns range if previous item was found, <code>null</code> otherwise
  7485. */
  7486. function getRangeForPrevItemInCSS(rule, offset, selRange) {
  7487. var tree = require('cssEditTree').parse(rule, {
  7488. offset: offset
  7489. });
  7490. var curRange = matchedRangeForCSSProperty(tree, selRange, true);
  7491. if(!curRange) {
  7492. // no matched property, try to match selector
  7493. var range = tree.nameRange(true);
  7494. if(selRange.start > range.start) {
  7495. return range;
  7496. }
  7497. }
  7498. return curRange;
  7499. }
  7500. // XXX register actions
  7501. var actions = require('actions');
  7502. actions.add('select_next_item', function(editor) {
  7503. if(editor.getSyntax() == 'css') return findNextCSSItem(editor);
  7504. else return findNextHTMLItem(editor);
  7505. });
  7506. actions.add('select_previous_item', function(editor) {
  7507. if(editor.getSyntax() == 'css') return findPrevCSSItem(editor);
  7508. else return findPrevHTMLItem(editor);
  7509. });
  7510. });
  7511. /**
  7512. * HTML pair matching (balancing) actions
  7513. * @constructor
  7514. * @memberOf __matchPairActionDefine
  7515. * @param {Function} require
  7516. * @param {Underscore} _
  7517. */
  7518. emmet.exec(function(require, _) { /** @type emmet.actions */
  7519. var actions = require('actions');
  7520. var matcher = require('html_matcher');
  7521. /**
  7522. * Find and select HTML tag pair
  7523. * @param {IEmmetEditor} editor Editor instance
  7524. * @param {String} direction Direction of pair matching: 'in' or 'out'.
  7525. * Default is 'out'
  7526. */
  7527. function matchPair(editor, direction, syntax) {
  7528. direction = String((direction || 'out').toLowerCase());
  7529. var info = require('editorUtils').outputInfo(editor, syntax);
  7530. syntax = info.syntax;
  7531. var range = require('range'); /** @type Range */
  7532. var selRange = range.create(editor.getSelectionRange());
  7533. var content = info.content; /** @type Range */
  7534. var tagRange = null; /** @type Range */
  7535. var _r;
  7536. var oldOpenTag = matcher.last_match['opening_tag'];
  7537. var oldCloseTag = matcher.last_match['closing_tag'];
  7538. if(direction == 'in' && oldOpenTag && selRange.length()) {
  7539. // user has previously selected tag and wants to move inward
  7540. if(!oldCloseTag) {
  7541. // unary tag was selected, can't move inward
  7542. return false;
  7543. } else if(oldOpenTag.start == selRange.start) {
  7544. if(content.charAt(oldOpenTag.end) == '<') {
  7545. // test if the first inward tag matches the entire parent tag's content
  7546. _r = range.create(matcher.find(content, oldOpenTag.end + 1, syntax));
  7547. if(_r.start == oldOpenTag.end && _r.end == oldCloseTag.start) {
  7548. tagRange = range.create(matcher(content, oldOpenTag.end + 1, syntax));
  7549. } else {
  7550. tagRange = range.create(oldOpenTag.end, oldCloseTag.start - oldOpenTag.end);
  7551. }
  7552. } else {
  7553. tagRange = range.create(oldOpenTag.end, oldCloseTag.start - oldOpenTag.end);
  7554. }
  7555. } else {
  7556. var newCursor = content.substring(0, oldCloseTag.start).indexOf('<', oldOpenTag.end);
  7557. var searchPos = newCursor != -1 ? newCursor + 1 : oldOpenTag.end;
  7558. tagRange = range.create(matcher(content, searchPos, syntax));
  7559. }
  7560. } else {
  7561. tagRange = range.create(matcher(content, selRange.end, syntax));
  7562. }
  7563. if(tagRange && tagRange.start != -1) {
  7564. editor.createSelection(tagRange.start, tagRange.end);
  7565. return true;
  7566. }
  7567. return false;
  7568. }
  7569. actions.add('match_pair', matchPair, {
  7570. hidden: true
  7571. });
  7572. actions.add('match_pair_inward', function(editor) {
  7573. return matchPair(editor, 'in');
  7574. }, {
  7575. label: 'HTML/Match Pair Tag (inward)'
  7576. });
  7577. actions.add('match_pair_outward', function(editor) {
  7578. return matchPair(editor, 'out');
  7579. }, {
  7580. label: 'HTML/Match Pair Tag (outward)'
  7581. });
  7582. /**
  7583. * Moves caret to matching opening or closing tag
  7584. * @param {IEmmetEditor} editor
  7585. */
  7586. actions.add('matching_pair', function(editor) {
  7587. var content = String(editor.getContent());
  7588. var caretPos = editor.getCaretPos();
  7589. if(content.charAt(caretPos) == '<')
  7590. // looks like caret is outside of tag pair
  7591. caretPos++;
  7592. var tags = matcher.getTags(content, caretPos, String(editor.getProfileName()));
  7593. if(tags && tags[0]) {
  7594. // match found
  7595. var openTag = tags[0];
  7596. var closeTag = tags[1];
  7597. if(closeTag) { // exclude unary tags
  7598. if(openTag.start <= caretPos && openTag.end >= caretPos) {
  7599. editor.setCaretPos(closeTag.start);
  7600. return true;
  7601. } else if(closeTag.start <= caretPos && closeTag.end >= caretPos) {
  7602. editor.setCaretPos(openTag.start);
  7603. return true;
  7604. }
  7605. }
  7606. }
  7607. return false;
  7608. }, {
  7609. label: 'HTML/Go To Matching Tag Pair'
  7610. });
  7611. });
  7612. /**
  7613. * Gracefully removes tag under cursor
  7614. *
  7615. * @param {Function} require
  7616. * @param {Underscore} _
  7617. */
  7618. emmet.exec(function(require, _) {
  7619. require('actions').add('remove_tag', function(editor) {
  7620. var utils = require('utils');
  7621. var info = require('editorUtils').outputInfo(editor);
  7622. // search for tag
  7623. var pair = require('html_matcher').getTags(info.content, editor.getCaretPos(), info.profile);
  7624. if(pair && pair[0]) {
  7625. if(!pair[1]) {
  7626. // simply remove unary tag
  7627. editor.replaceContent(utils.getCaretPlaceholder(), pair[0].start, pair[0].end);
  7628. } else {
  7629. // remove tag and its newlines
  7630. /** @type Range */
  7631. var tagContentRange = utils.narrowToNonSpace(info.content, pair[0].end, pair[1].start - pair[0].end); /** @type Range */
  7632. var startLineBounds = utils.findNewlineBounds(info.content, tagContentRange.start);
  7633. var startLinePad = utils.getLinePadding(startLineBounds.substring(info.content));
  7634. var tagContent = tagContentRange.substring(info.content);
  7635. tagContent = utils.unindentString(tagContent, startLinePad);
  7636. editor.replaceContent(utils.getCaretPlaceholder() + utils.escapeText(tagContent), pair[0].start, pair[1].end);
  7637. }
  7638. return true;
  7639. }
  7640. return false;
  7641. }, {
  7642. label: 'HTML/Remove Tag'
  7643. });
  7644. });
  7645. /**
  7646. * Splits or joins tag, e.g. transforms it into a short notation and vice versa:<br>
  7647. * &lt;div&gt;&lt;/div&gt; → &lt;div /&gt; : join<br>
  7648. * &lt;div /&gt; → &lt;div&gt;&lt;/div&gt; : split
  7649. * @param {Function} require
  7650. * @param {Underscore} _
  7651. * @memberOf __splitJoinTagAction
  7652. * @constructor
  7653. */
  7654. emmet.exec(function(require, _) {
  7655. /**
  7656. * @param {IEmmetEditor} editor
  7657. * @param {Object} profile
  7658. * @param {Object} htmlMatch
  7659. */
  7660. function joinTag(editor, profile, htmlMatch) { /** @type emmet.utils */
  7661. var utils = require('utils');
  7662. var closingSlash = (profile.self_closing_tag === true) ? '/' : ' /';
  7663. var content = htmlMatch[0].full_tag.replace(/\s*>$/, closingSlash + '>');
  7664. // add caret placeholder
  7665. if(content.length + htmlMatch[0].start < editor.getCaretPos()) content += utils.getCaretPlaceholder();
  7666. else {
  7667. var d = editor.getCaretPos() - htmlMatch[0].start;
  7668. content = utils.replaceSubstring(content, utils.getCaretPlaceholder(), d);
  7669. }
  7670. editor.replaceContent(content, htmlMatch[0].start, htmlMatch[1].end);
  7671. return true;
  7672. }
  7673. function splitTag(editor, profile, htmlMatch) { /** @type emmet.utils */
  7674. var utils = require('utils');
  7675. var nl = utils.getNewline();
  7676. var pad = require('resources').getVariable('indentation');
  7677. var caret = utils.getCaretPlaceholder();
  7678. // define tag content depending on profile
  7679. var tagContent = (profile.tag_nl === true) ? nl + pad + caret + nl : caret;
  7680. var content = htmlMatch[0].full_tag.replace(/\s*\/>$/, '>') + tagContent + '</' + htmlMatch[0].name + '>';
  7681. editor.replaceContent(content, htmlMatch[0].start, htmlMatch[0].end);
  7682. return true;
  7683. }
  7684. require('actions').add('split_join_tag', function(editor, profileName) {
  7685. var matcher = require('html_matcher');
  7686. var info = require('editorUtils').outputInfo(editor, null, profileName);
  7687. var profile = require('profile').get(info.profile);
  7688. // find tag at current position
  7689. var pair = matcher.getTags(info.content, editor.getCaretPos(), info.profile);
  7690. if(pair && pair[0]) {
  7691. if(pair[1]) { // join tag
  7692. return joinTag(editor, profile, pair);
  7693. }
  7694. return splitTag(editor, profile, pair);
  7695. }
  7696. return false;
  7697. }, {
  7698. label: 'HTML/Split\\Join Tag Declaration'
  7699. });
  7700. });
  7701. /**
  7702. * Reflect CSS value: takes rule's value under caret and pastes it for the same
  7703. * rules with vendor prefixes
  7704. * @constructor
  7705. * @memberOf __reflectCSSActionDefine
  7706. * @param {Function} require
  7707. * @param {Underscore} _
  7708. */
  7709. emmet.define('reflectCSSValue', function(require, _) {
  7710. /**
  7711. * @type HandlerList List of registered handlers
  7712. */
  7713. var handlers = require('handlerList').create();
  7714. require('actions').add('reflect_css_value', function(editor) {
  7715. if(editor.getSyntax() != 'css') return false;
  7716. return require('actionUtils').compoundUpdate(editor, doCSSReflection(editor));
  7717. }, {
  7718. label: 'CSS/Reflect Value'
  7719. });
  7720. function doCSSReflection(editor) { /** @type emmet.cssEditTree */
  7721. var cssEditTree = require('cssEditTree');
  7722. var outputInfo = require('editorUtils').outputInfo(editor);
  7723. var caretPos = editor.getCaretPos();
  7724. var cssRule = cssEditTree.parseFromPosition(outputInfo.content, caretPos);
  7725. if(!cssRule) return;
  7726. var property = cssRule.itemFromPosition(caretPos, true);
  7727. // no property under cursor, nothing to reflect
  7728. if(!property) return;
  7729. var oldRule = cssRule.source;
  7730. var offset = cssRule.options.offset;
  7731. var caretDelta = caretPos - offset - property.range().start;
  7732. handlers.exec(false, [property]);
  7733. if(oldRule !== cssRule.source) {
  7734. return {
  7735. data: cssRule.source,
  7736. start: offset,
  7737. end: offset + oldRule.length,
  7738. caret: offset + property.range().start + caretDelta
  7739. };
  7740. }
  7741. }
  7742. /**
  7743. * Returns regexp that should match reflected CSS property names
  7744. * @param {String} name Current CSS property name
  7745. * @return {RegExp}
  7746. */
  7747. function getReflectedCSSName(name) {
  7748. name = require('cssEditTree').baseName(name);
  7749. var vendorPrefix = '^(?:\\-\\w+\\-)?',
  7750. m;
  7751. if(name == 'opacity' || name == 'filter') {
  7752. return new RegExp(vendorPrefix + '(?:opacity|filter)$');
  7753. } else if(m = name.match(/^border-radius-(top|bottom)(left|right)/)) {
  7754. // Mozilla-style border radius
  7755. return new RegExp(vendorPrefix + '(?:' + name + '|border-' + m[1] + '-' + m[2] + '-radius)$');
  7756. } else if(m = name.match(/^border-(top|bottom)-(left|right)-radius/)) {
  7757. return new RegExp(vendorPrefix + '(?:' + name + '|border-radius-' + m[1] + m[2] + ')$');
  7758. }
  7759. return new RegExp(vendorPrefix + name + '$');
  7760. }
  7761. /**
  7762. * Reflects value from <code>donor</code> into <code>receiver</code>
  7763. * @param {CSSProperty} donor Donor CSS property from which value should
  7764. * be reflected
  7765. * @param {CSSProperty} receiver Property that should receive reflected
  7766. * value from donor
  7767. */
  7768. function reflectValue(donor, receiver) {
  7769. var value = getReflectedValue(donor.name(), donor.value(), receiver.name(), receiver.value());
  7770. receiver.value(value);
  7771. }
  7772. /**
  7773. * Returns value that should be reflected for <code>refName</code> CSS property
  7774. * from <code>curName</code> property. This function is used for special cases,
  7775. * when the same result must be achieved with different properties for different
  7776. * browsers. For example: opаcity:0.5; → filter:alpha(opacity=50);<br><br>
  7777. *
  7778. * This function does value conversion between different CSS properties
  7779. *
  7780. * @param {String} curName Current CSS property name
  7781. * @param {String} curValue Current CSS property value
  7782. * @param {String} refName Receiver CSS property's name
  7783. * @param {String} refValue Receiver CSS property's value
  7784. * @return {String} New value for receiver property
  7785. */
  7786. function getReflectedValue(curName, curValue, refName, refValue) {
  7787. var cssEditTree = require('cssEditTree');
  7788. var utils = require('utils');
  7789. curName = cssEditTree.baseName(curName);
  7790. refName = cssEditTree.baseName(refName);
  7791. if(curName == 'opacity' && refName == 'filter') {
  7792. return refValue.replace(/opacity=[^)]*/i, 'opacity=' + Math.floor(parseFloat(curValue) * 100));
  7793. } else if(curName == 'filter' && refName == 'opacity') {
  7794. var m = curValue.match(/opacity=([^)]*)/i);
  7795. return m ? utils.prettifyNumber(parseInt(m[1]) / 100) : refValue;
  7796. }
  7797. return curValue;
  7798. }
  7799. // XXX add default handler
  7800. handlers.add(function(property) {
  7801. var reName = getReflectedCSSName(property.name());
  7802. _.each(property.parent.list(), function(p) {
  7803. if(reName.test(p.name())) {
  7804. reflectValue(property, p);
  7805. }
  7806. });
  7807. }, {
  7808. order: -1
  7809. });
  7810. return {
  7811. /**
  7812. * Adds custom reflect handler. The passed function will receive matched
  7813. * CSS property (as <code>CSSEditElement</code> object) and should
  7814. * return <code>true</code> if it was performed successfully (handled
  7815. * reflection), <code>false</code> otherwise.
  7816. * @param {Function} fn
  7817. * @param {Object} options
  7818. */
  7819. addHandler: function(fn, options) {
  7820. handlers.add(fn, options);
  7821. },
  7822. /**
  7823. * Removes registered handler
  7824. * @returns
  7825. */
  7826. removeHandler: function(fn) {
  7827. handlers.remove(fn, options);
  7828. }
  7829. };
  7830. });
  7831. /**
  7832. * Evaluates simple math expression under caret
  7833. * @param {Function} require
  7834. * @param {Underscore} _
  7835. */
  7836. emmet.exec(function(require, _) {
  7837. require('actions').add('evaluate_math_expression', function(editor) {
  7838. var actionUtils = require('actionUtils');
  7839. var utils = require('utils');
  7840. var content = String(editor.getContent());
  7841. var chars = '.+-*/\\';
  7842. /** @type Range */
  7843. var sel = require('range').create(editor.getSelectionRange());
  7844. if(!sel.length()) {
  7845. sel = actionUtils.findExpressionBounds(editor, function(ch) {
  7846. return utils.isNumeric(ch) || chars.indexOf(ch) != -1;
  7847. });
  7848. }
  7849. if(sel && sel.length()) {
  7850. var expr = sel.substring(content);
  7851. // replace integral division: 11\2 => Math.round(11/2)
  7852. expr = expr.replace(/([\d\.\-]+)\\([\d\.\-]+)/g, 'Math.round($1/$2)');
  7853. try {
  7854. var result = utils.prettifyNumber(new Function('return ' + expr)());
  7855. editor.replaceContent(result, sel.start, sel.end);
  7856. editor.setCaretPos(sel.start + result.length);
  7857. return true;
  7858. } catch(e) {}
  7859. }
  7860. return false;
  7861. }, {
  7862. label: 'Numbers/Evaluate Math Expression'
  7863. });
  7864. });
  7865. /**
  7866. * Increment/decrement number under cursor
  7867. * @param {Function} require
  7868. * @param {Underscore} _
  7869. */
  7870. emmet.exec(function(require, _) {
  7871. /**
  7872. * Extract number from current caret position of the <code>editor</code> and
  7873. * increment it by <code>step</code>
  7874. * @param {IEmmetEditor} editor
  7875. * @param {Number} step Increment step (may be negative)
  7876. */
  7877. function incrementNumber(editor, step) {
  7878. var utils = require('utils');
  7879. var actionUtils = require('actionUtils');
  7880. var hasSign = false;
  7881. var hasDecimal = false;
  7882. var r = actionUtils.findExpressionBounds(editor, function(ch, pos, content) {
  7883. if(utils.isNumeric(ch)) return true;
  7884. if(ch == '.') {
  7885. // make sure that next character is numeric too
  7886. if(!utils.isNumeric(content.charAt(pos + 1))) return false;
  7887. return hasDecimal ? false : hasDecimal = true;
  7888. }
  7889. if(ch == '-') return hasSign ? false : hasSign = true;
  7890. return false;
  7891. });
  7892. if(r && r.length()) {
  7893. var strNum = r.substring(String(editor.getContent()));
  7894. var num = parseFloat(strNum);
  7895. if(!_.isNaN(num)) {
  7896. num = utils.prettifyNumber(num + step);
  7897. // do we have zero-padded number?
  7898. if(/^(\-?)0+[1-9]/.test(strNum)) {
  7899. var minus = '';
  7900. if(RegExp.$1) {
  7901. minus = '-';
  7902. num = num.substring(1);
  7903. }
  7904. var parts = num.split('.');
  7905. parts[0] = utils.zeroPadString(parts[0], intLength(strNum));
  7906. num = minus + parts.join('.');
  7907. }
  7908. editor.replaceContent(num, r.start, r.end);
  7909. editor.createSelection(r.start, r.start + num.length);
  7910. return true;
  7911. }
  7912. }
  7913. return false;
  7914. }
  7915. /**
  7916. * Returns length of integer part of number
  7917. * @param {String} num
  7918. */
  7919. function intLength(num) {
  7920. num = num.replace(/^\-/, '');
  7921. if(~num.indexOf('.')) {
  7922. return num.split('.')[0].length;
  7923. }
  7924. return num.length;
  7925. }
  7926. var actions = require('actions');
  7927. _.each([1, -1, 10, -10, 0.1, -0.1], function(num) {
  7928. var prefix = num > 0 ? 'increment' : 'decrement';
  7929. actions.add(prefix + '_number_by_' + String(Math.abs(num)).replace('.', '').substring(0, 2), function(editor) {
  7930. return incrementNumber(editor, num);
  7931. }, {
  7932. label: 'Numbers/' + prefix.charAt(0).toUpperCase() + prefix.substring(1) + ' number by ' + Math.abs(num)
  7933. });
  7934. });
  7935. });
  7936. /**
  7937. * Actions to insert line breaks. Some simple editors (like browser's
  7938. * &lt;textarea&gt;, for example) do not provide such simple things
  7939. * @param {Function} require
  7940. * @param {Underscore} _
  7941. */
  7942. emmet.exec(function(require, _) {
  7943. var actions = require('actions'); /** @type emmet.preferences */
  7944. var prefs = require('preferences');
  7945. // setup default preferences
  7946. prefs.define('css.closeBraceIndentation', '\n', 'Indentation before closing brace of CSS rule. Some users prefere ' + 'indented closing brace of CSS rule for better readability. ' + 'This preference’s value will be automatically inserted before ' + 'closing brace when user adds newline in newly created CSS rule ' + '(e.g. when “Insert formatted linebreak” action will be performed ' + 'in CSS file). If you’re such user, you may want to write put a value ' + 'like <code>\\n\\t</code> in this preference.');
  7947. /**
  7948. * Inserts newline character with proper indentation in specific positions only.
  7949. * @param {IEmmetEditor} editor
  7950. * @return {Boolean} Returns <code>true</code> if line break was inserted
  7951. */
  7952. actions.add('insert_formatted_line_break_only', function(editor) {
  7953. var utils = require('utils'); /** @type emmet.resources */
  7954. var res = require('resources');
  7955. var info = require('editorUtils').outputInfo(editor);
  7956. var caretPos = editor.getCaretPos();
  7957. var nl = utils.getNewline();
  7958. if(_.include(['html', 'xml', 'xsl'], info.syntax)) {
  7959. var pad = res.getVariable('indentation');
  7960. // let's see if we're breaking newly created tag
  7961. var pair = require('html_matcher').getTags(info.content, caretPos, info.profile);
  7962. if(pair[0] && pair[1] && pair[0].type == 'tag' && pair[0].end == caretPos && pair[1].start == caretPos) {
  7963. editor.replaceContent(nl + pad + utils.getCaretPlaceholder() + nl, caretPos);
  7964. return true;
  7965. }
  7966. } else if(info.syntax == 'css') { /** @type String */
  7967. var content = info.content;
  7968. if(caretPos && content.charAt(caretPos - 1) == '{') {
  7969. var append = prefs.get('css.closeBraceIndentation');
  7970. var pad = res.getVariable('indentation');
  7971. var hasCloseBrace = content.charAt(caretPos) == '}';
  7972. if(!hasCloseBrace) {
  7973. // do we really need special formatting here?
  7974. // check if this is really a newly created rule,
  7975. // look ahead for a closing brace
  7976. for(var i = caretPos, il = content.length, ch; i < il; i++) {
  7977. ch = content.charAt(i);
  7978. if(ch == '{') {
  7979. // ok, this is a new rule without closing brace
  7980. break;
  7981. }
  7982. if(ch == '}') {
  7983. // not a new rule, just add indentation
  7984. append = '';
  7985. hasCloseBrace = true;
  7986. break;
  7987. }
  7988. }
  7989. }
  7990. if(!hasCloseBrace) {
  7991. append += '}';
  7992. }
  7993. // defining rule set
  7994. var insValue = nl + pad + utils.getCaretPlaceholder() + append;
  7995. editor.replaceContent(insValue, caretPos);
  7996. return true;
  7997. }
  7998. }
  7999. return false;
  8000. }, {
  8001. hidden: true
  8002. });
  8003. /**
  8004. * Inserts newline character with proper indentation. This action is used in
  8005. * editors that doesn't have indentation control (like textarea element) to
  8006. * provide proper indentation
  8007. * @param {IEmmetEditor} editor Editor instance
  8008. */
  8009. actions.add('insert_formatted_line_break', function(editor) {
  8010. if(!actions.run('insert_formatted_line_break_only', editor)) {
  8011. var utils = require('utils');
  8012. var curPadding = require('editorUtils').getCurrentLinePadding(editor);
  8013. var content = String(editor.getContent());
  8014. var caretPos = editor.getCaretPos();
  8015. var len = content.length;
  8016. var nl = utils.getNewline();
  8017. // check out next line padding
  8018. var lineRange = editor.getCurrentLineRange();
  8019. var nextPadding = '';
  8020. for(var i = lineRange.end + 1, ch; i < len; i++) {
  8021. ch = content.charAt(i);
  8022. if(ch == ' ' || ch == '\t') nextPadding += ch;
  8023. else break;
  8024. }
  8025. if(nextPadding.length > curPadding.length) editor.replaceContent(nl + nextPadding, caretPos, caretPos, true);
  8026. else editor.replaceContent(nl, caretPos);
  8027. }
  8028. return true;
  8029. }, {
  8030. hidden: true
  8031. });
  8032. });
  8033. /**
  8034. * Merges selected lines or lines between XHTML tag pairs
  8035. * @param {Function} require
  8036. * @param {Underscore} _
  8037. */
  8038. emmet.exec(function(require, _) {
  8039. require('actions').add('merge_lines', function(editor) {
  8040. var matcher = require('html_matcher');
  8041. var utils = require('utils');
  8042. var editorUtils = require('editorUtils');
  8043. var info = editorUtils.outputInfo(editor);
  8044. /** @type Range */
  8045. var selection = require('range').create(editor.getSelectionRange());
  8046. if(!selection.length()) {
  8047. // find matching tag
  8048. var pair = matcher(info.content, editor.getCaretPos(), info.profile);
  8049. if(pair) {
  8050. selection.start = pair[0];
  8051. selection.end = pair[1];
  8052. }
  8053. }
  8054. if(selection.length()) {
  8055. // got range, merge lines
  8056. var text = selection.substring(info.content);
  8057. var lines = utils.splitByLines(text);
  8058. for(var i = 1; i < lines.length; i++) {
  8059. lines[i] = lines[i].replace(/^\s+/, '');
  8060. }
  8061. text = lines.join('').replace(/\s{2,}/, ' ');
  8062. editor.replaceContent(text, selection.start, selection.end);
  8063. editor.createSelection(selection.start, selection.start + text.length);
  8064. return true;
  8065. }
  8066. return false;
  8067. });
  8068. });
  8069. /**
  8070. * Encodes/decodes image under cursor to/from base64
  8071. * @param {IEmmetEditor} editor
  8072. * @since 0.65
  8073. *
  8074. * @memberOf __base64ActionDefine
  8075. * @constructor
  8076. * @param {Function} require
  8077. * @param {Underscore} _
  8078. */
  8079. emmet.exec(function(require, _) {
  8080. require('actions').add('encode_decode_data_url', function(editor) {
  8081. var data = String(editor.getSelection());
  8082. var caretPos = editor.getCaretPos();
  8083. if(!data) {
  8084. // no selection, try to find image bounds from current caret position
  8085. var text = String(editor.getContent()),
  8086. m;
  8087. while(caretPos-- >= 0) {
  8088. if(startsWith('src=', text, caretPos)) { // found <img src="">
  8089. if(m = text.substr(caretPos).match(/^(src=(["'])?)([^'"<>\s]+)\1?/)) {
  8090. data = m[3];
  8091. caretPos += m[1].length;
  8092. }
  8093. break;
  8094. } else if(startsWith('url(', text, caretPos)) { // found CSS url() pattern
  8095. if(m = text.substr(caretPos).match(/^(url\((['"])?)([^'"\)\s]+)\1?/)) {
  8096. data = m[3];
  8097. caretPos += m[1].length;
  8098. }
  8099. break;
  8100. }
  8101. }
  8102. }
  8103. if(data) {
  8104. if(startsWith('data:', data)) return decodeFromBase64(editor, data, caretPos);
  8105. else return encodeToBase64(editor, data, caretPos);
  8106. }
  8107. return false;
  8108. }, {
  8109. label: 'Encode\\Decode data:URL image'
  8110. });
  8111. /**
  8112. * Test if <code>text</code> starts with <code>token</code> at <code>pos</code>
  8113. * position. If <code>pos</code> is omitted, search from beginning of text
  8114. * @param {String} token Token to test
  8115. * @param {String} text Where to search
  8116. * @param {Number} pos Position where to start search
  8117. * @return {Boolean}
  8118. * @since 0.65
  8119. */
  8120. function startsWith(token, text, pos) {
  8121. pos = pos || 0;
  8122. return text.charAt(pos) == token.charAt(0) && text.substr(pos, token.length) == token;
  8123. }
  8124. /**
  8125. * Encodes image to base64
  8126. *
  8127. * @param {IEmmetEditor} editor
  8128. * @param {String} imgPath Path to image
  8129. * @param {Number} pos Caret position where image is located in the editor
  8130. * @return {Boolean}
  8131. */
  8132. function encodeToBase64(editor, imgPath, pos) {
  8133. var file = require('file');
  8134. var actionUtils = require('actionUtils');
  8135. var editorFile = editor.getFilePath();
  8136. var defaultMimeType = 'application/octet-stream';
  8137. if(editorFile === null) {
  8138. throw "You should save your file before using this action";
  8139. }
  8140. // locate real image path
  8141. var realImgPath = file.locateFile(editorFile, imgPath);
  8142. if(realImgPath === null) {
  8143. throw "Can't find " + imgPath + ' file';
  8144. }
  8145. var b64 = require('base64').encode(String(file.read(realImgPath)));
  8146. if(!b64) {
  8147. throw "Can't encode file content to base64";
  8148. }
  8149. b64 = 'data:' + (actionUtils.mimeTypes[String(file.getExt(realImgPath))] || defaultMimeType) + ';base64,' + b64;
  8150. editor.replaceContent('$0' + b64, pos, pos + imgPath.length);
  8151. return true;
  8152. }
  8153. /**
  8154. * Decodes base64 string back to file.
  8155. * @param {IEmmetEditor} editor
  8156. * @param {String} data Base64-encoded file content
  8157. * @param {Number} pos Caret position where image is located in the editor
  8158. */
  8159. function decodeFromBase64(editor, data, pos) {
  8160. // ask user to enter path to file
  8161. var filePath = String(editor.prompt('Enter path to file (absolute or relative)'));
  8162. if(!filePath) return false;
  8163. var file = require('file');
  8164. var absPath = file.createPath(editor.getFilePath(), filePath);
  8165. if(!absPath) {
  8166. throw "Can't save file";
  8167. }
  8168. file.save(absPath, require('base64').decode(data.replace(/^data\:.+?;.+?,/, '')));
  8169. editor.replaceContent('$0' + filePath, pos, pos + data.length);
  8170. return true;
  8171. }
  8172. });
  8173. /**
  8174. * Automatically updates image size attributes in HTML's &lt;img&gt; element or
  8175. * CSS rule
  8176. * @param {Function} require
  8177. * @param {Underscore} _
  8178. * @constructor
  8179. * @memberOf __updateImageSizeAction
  8180. */
  8181. emmet.exec(function(require, _) {
  8182. /**
  8183. * Updates image size of &lt;img src=""&gt; tag
  8184. * @param {IEmmetEditor} editor
  8185. */
  8186. function updateImageSizeHTML(editor) {
  8187. var offset = editor.getCaretPos();
  8188. // find tag from current caret position
  8189. var info = require('editorUtils').outputInfo(editor);
  8190. var xmlElem = require('xmlEditTree').parseFromPosition(info.content, offset, true);
  8191. if(xmlElem && (xmlElem.name() || '').toLowerCase() == 'img') {
  8192. var size = getImageSizeForSource(editor, xmlElem.value('src'));
  8193. if(size) {
  8194. var compoundData = xmlElem.range(true);
  8195. xmlElem.value('width', size.width);
  8196. xmlElem.value('height', size.height, xmlElem.indexOf('width') + 1);
  8197. return _.extend(compoundData, {
  8198. data: xmlElem.toString(),
  8199. caret: offset
  8200. });
  8201. }
  8202. }
  8203. return null;
  8204. }
  8205. /**
  8206. * Updates image size of CSS property
  8207. * @param {IEmmetEditor} editor
  8208. */
  8209. function updateImageSizeCSS(editor) {
  8210. var offset = editor.getCaretPos();
  8211. // find tag from current caret position
  8212. var info = require('editorUtils').outputInfo(editor);
  8213. var cssRule = require('cssEditTree').parseFromPosition(info.content, offset, true);
  8214. if(cssRule) {
  8215. // check if there is property with image under caret
  8216. var prop = cssRule.itemFromPosition(offset, true),
  8217. m;
  8218. if(prop && (m = /url\((["']?)(.+?)\1\)/i.exec(prop.value() || ''))) {
  8219. var size = getImageSizeForSource(editor, m[2]);
  8220. if(size) {
  8221. var compoundData = cssRule.range(true);
  8222. cssRule.value('width', size.width + 'px');
  8223. cssRule.value('height', size.height + 'px', cssRule.indexOf('width') + 1);
  8224. return _.extend(compoundData, {
  8225. data: cssRule.toString(),
  8226. caret: offset
  8227. });
  8228. }
  8229. }
  8230. }
  8231. return null;
  8232. }
  8233. /**
  8234. * Returns image dimensions for source
  8235. * @param {IEmmetEditor} editor
  8236. * @param {String} src Image source (path or data:url)
  8237. */
  8238. function getImageSizeForSource(editor, src) {
  8239. var fileContent;
  8240. if(src) {
  8241. // check if it is data:url
  8242. if(/^data:/.test(src)) {
  8243. fileContent = require('base64').decode(src.replace(/^data\:.+?;.+?,/, ''));
  8244. } else {
  8245. var file = require('file');
  8246. var absPath = file.locateFile(editor.getFilePath(), src);
  8247. if(absPath === null) {
  8248. throw "Can't find " + src + ' file';
  8249. }
  8250. fileContent = String(file.read(absPath));
  8251. }
  8252. return require('actionUtils').getImageSize(fileContent);
  8253. }
  8254. }
  8255. require('actions').add('update_image_size', function(editor) {
  8256. var result;
  8257. if(String(editor.getSyntax()) == 'css') {
  8258. result = updateImageSizeCSS(editor);
  8259. } else {
  8260. result = updateImageSizeHTML(editor);
  8261. }
  8262. return require('actionUtils').compoundUpdate(editor, result);
  8263. });
  8264. });
  8265. /**
  8266. * Resolver for fast CSS typing. Handles abbreviations with the following
  8267. * notation:<br>
  8268. *
  8269. * <code>(-vendor prefix)?property(value)*(!)?</code>
  8270. *
  8271. * <br><br>
  8272. * <b>Abbreviation handling</b><br>
  8273. *
  8274. * By default, Emmet searches for matching snippet definition for provided abbreviation.
  8275. * If snippet wasn't found, Emmet automatically generates element with
  8276. * abbreviation's name. For example, <code>foo</code> abbreviation will generate
  8277. * <code>&lt;foo&gt;&lt;/foo&gt;</code> output.
  8278. * <br><br>
  8279. * This module will capture all expanded properties and upgrade them with values,
  8280. * vendor prefixes and !important declarations. All unmatched abbreviations will
  8281. * be automatically transformed into <code>property-name: ${1}</code> snippets.
  8282. *
  8283. * <b>Vendor prefixes<b><br>
  8284. *
  8285. * If CSS-property is preceded with dash, resolver should output property with
  8286. * all <i>known</i> vendor prefixes. For example, if <code>brad</code>
  8287. * abbreviation generates <code>border-radius: ${value};</code> snippet,
  8288. * the <code>-brad</code> abbreviation should generate:
  8289. * <pre><code>
  8290. * -webkit-border-radius: ${value};
  8291. * -moz-border-radius: ${value};
  8292. * border-radius: ${value};
  8293. * </code></pre>
  8294. * Note that <i>o</i> and <i>ms</i> prefixes are omitted since Opera and IE
  8295. * supports unprefixed property.<br><br>
  8296. *
  8297. * Users can also provide an explicit list of one-character prefixes for any
  8298. * CSS property. For example, <code>-wm-float</code> will produce
  8299. *
  8300. * <pre><code>
  8301. * -webkit-float: ${1};
  8302. * -moz-float: ${1};
  8303. * float: ${1};
  8304. * </code></pre>
  8305. *
  8306. * Although this example looks pointless, users can use this feature to write
  8307. * cutting-edge properties implemented by browser vendors recently.
  8308. *
  8309. * @constructor
  8310. * @memberOf __cssResolverDefine
  8311. * @param {Function} require
  8312. * @param {Underscore} _
  8313. */
  8314. emmet.define('cssResolver', function(require, _) { /** Back-reference to module */
  8315. var module = null;
  8316. var prefixObj = { /** Real vendor prefix name */
  8317. prefix: 'emmet',
  8318. /**
  8319. * Indicates this prefix is obsolete and should't be used when user
  8320. * wants to generate all-prefixed properties
  8321. */
  8322. obsolete: false,
  8323. /**
  8324. * Returns prefixed CSS property name
  8325. * @param {String} name Unprefixed CSS property
  8326. */
  8327. transformName: function(name) {
  8328. return '-' + this.prefix + '-' + name;
  8329. },
  8330. /**
  8331. * @type {Array} List of unprefixed CSS properties that supported by
  8332. * current prefix. This list is used to generate all-prefixed property
  8333. */
  8334. supports: null
  8335. };
  8336. /**
  8337. * List of registered one-character prefixes. Key is a one-character prefix,
  8338. * value is an <code>prefixObj</code> object
  8339. */
  8340. var vendorPrefixes = {};
  8341. var defaultValue = '${1};';
  8342. // XXX module preferences
  8343. var prefs = require('preferences');
  8344. prefs.define('css.valueSeparator', ': ', 'Defines a symbol that should be placed between CSS property and ' + 'value when expanding CSS abbreviations.');
  8345. prefs.define('css.propertyEnd', ';', 'Defines a symbol that should be placed at the end of CSS property ' + 'when expanding CSS abbreviations.');
  8346. prefs.define('stylus.valueSeparator', ' ', 'Defines a symbol that should be placed between CSS property and ' + 'value when expanding CSS abbreviations in Stylus dialect.');
  8347. prefs.define('stylus.propertyEnd', '', 'Defines a symbol that should be placed at the end of CSS property ' + 'when expanding CSS abbreviations in Stylus dialect.');
  8348. prefs.define('sass.propertyEnd', '', 'Defines a symbol that should be placed at the end of CSS property ' + 'when expanding CSS abbreviations in SASS dialect.');
  8349. prefs.define('css.autoInsertVendorPrefixes', true, 'Automatically generate vendor-prefixed copies of expanded CSS ' + 'property. By default, Emmet will generate vendor-prefixed ' + 'properties only when you put dash before abbreviation ' + '(e.g. <code>-bxsh</code>). With this option enabled, you don’t ' + 'need dashes before abbreviations: Emmet will produce ' + 'vendor-prefixed properties for you.');
  8350. var descTemplate = _.template('A comma-separated list of CSS properties that may have ' + '<code><%= vendor %></code> vendor prefix. This list is used to generate ' + 'a list of prefixed properties when expanding <code>-property</code> ' + 'abbreviations. Empty list means that all possible CSS values may ' + 'have <code><%= vendor %></code> prefix.');
  8351. var descAddonTemplate = _.template('A comma-separated list of <em>additions</em> CSS properties ' + 'for <code>css.<%= vendor %>Preperties</code> preference. ' + 'You should use this list if you want to add or remove a few CSS ' + 'properties to original set. To add a new property, simply write its name, ' + 'to remove it, precede property with hyphen.<br>' + 'For example, to add <em>foo</em> property and remove <em>border-radius</em> one, ' + 'the preference value will look like this: <code>foo, -border-radius</code>.');
  8352. // properties list is created from cssFeatures.html file
  8353. var props = {
  8354. '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',
  8355. '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',
  8356. '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',
  8357. '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'
  8358. };
  8359. _.each(props, function(v, k) {
  8360. prefs.define('css.' + k + 'Properties', v, descTemplate({
  8361. vendor: k
  8362. }));
  8363. prefs.define('css.' + k + 'PropertiesAddon', '', descAddonTemplate({
  8364. vendor: k
  8365. }));
  8366. });
  8367. prefs.define('css.unitlessProperties', 'z-index, line-height, opacity, font-weight, zoom', 'The list of properties whose values ​​must not contain units.');
  8368. prefs.define('css.intUnit', 'px', 'Default unit for integer values');
  8369. prefs.define('css.floatUnit', 'em', 'Default unit for float values');
  8370. prefs.define('css.keywords', 'auto, inherit', 'A comma-separated list of valid keywords that can be used in CSS abbreviations.');
  8371. prefs.define('css.keywordAliases', 'a:auto, i:inherit', 'A comma-separated list of keyword aliases, used in CSS abbreviation. ' + 'Each alias should be defined as <code>alias:keyword_name</code>.');
  8372. prefs.define('css.unitAliases', 'e:em, p:%, x:ex, r:rem', 'A comma-separated list of unit aliases, used in CSS abbreviation. ' + 'Each alias should be defined as <code>alias:unit_value</code>.');
  8373. function isNumeric(ch) {
  8374. var code = ch && ch.charCodeAt(0);
  8375. return(ch && ch == '.' || (code > 47 && code < 58));
  8376. }
  8377. /**
  8378. * Check if provided snippet contains only one CSS property and value.
  8379. * @param {String} snippet
  8380. * @returns {Boolean}
  8381. */
  8382. function isSingleProperty(snippet) {
  8383. var utils = require('utils');
  8384. snippet = utils.trim(snippet);
  8385. // check if it doesn't contain a comment and a newline
  8386. if(~snippet.indexOf('/*') || /[\n\r]/.test(snippet)) {
  8387. return false;
  8388. }
  8389. // check if it's a valid snippet definition
  8390. if(!/^[a-z0-9\-]+\s*\:/i.test(snippet)) {
  8391. return false;
  8392. }
  8393. snippet = require('tabStops').processText(snippet, {
  8394. replaceCarets: true,
  8395. tabstop: function() {
  8396. return 'value';
  8397. }
  8398. });
  8399. return snippet.split(':').length == 2;
  8400. }
  8401. function getKeyword(name) {
  8402. var aliases = prefs.getDict('css.keywordAliases');
  8403. return name in aliases ? aliases[name] : name;
  8404. }
  8405. function getUnit(name) {
  8406. var aliases = prefs.getDict('css.unitAliases');
  8407. return name in aliases ? aliases[name] : name;
  8408. }
  8409. function isValidKeyword(keyword) {
  8410. return _.include(prefs.getArray('css.keywords'), getKeyword(keyword));
  8411. }
  8412. /**
  8413. * Split snippet into a CSS property-value pair
  8414. * @param {String} snippet
  8415. */
  8416. function splitSnippet(snippet) {
  8417. var utils = require('utils');
  8418. snippet = utils.trim(snippet);
  8419. if(snippet.indexOf(':') == -1) {
  8420. return {
  8421. name: snippet,
  8422. value: defaultValue
  8423. };
  8424. }
  8425. var pair = snippet.split(':');
  8426. return {
  8427. name: utils.trim(pair.shift()),
  8428. // replace ${0} tabstop to produce valid vendor-prefixed values
  8429. // where possible
  8430. value: utils.trim(pair.join(':')).replace(/^(\$\{0\}|\$0)(\s*;?)$/, '${1}$2')
  8431. };
  8432. }
  8433. /**
  8434. * Check if passed CSS property support specified vendor prefix
  8435. * @param {String} property
  8436. * @param {String} prefix
  8437. */
  8438. function hasPrefix(property, prefix) {
  8439. var info = vendorPrefixes[prefix];
  8440. if(!info) info = _.find(vendorPrefixes, function(data) {
  8441. return data.prefix == prefix;
  8442. });
  8443. return info && info.supports && _.include(info.supports, property);
  8444. }
  8445. /**
  8446. * Search for a list of supported prefixes for CSS property. This list
  8447. * is used to generate all-prefixed snippet
  8448. * @param {String} property CSS property name
  8449. * @returns {Array}
  8450. */
  8451. function findPrefixes(property, noAutofill) {
  8452. var result = [];
  8453. _.each(vendorPrefixes, function(obj, prefix) {
  8454. if(hasPrefix(property, prefix)) {
  8455. result.push(prefix);
  8456. }
  8457. });
  8458. if(!result.length && !noAutofill) {
  8459. // add all non-obsolete prefixes
  8460. _.each(vendorPrefixes, function(obj, prefix) {
  8461. if(!obj.obsolete) result.push(prefix);
  8462. });
  8463. }
  8464. return result;
  8465. }
  8466. function addPrefix(name, obj) {
  8467. if(_.isString(obj)) obj = {
  8468. prefix: obj
  8469. };
  8470. vendorPrefixes[name] = _.extend({}, prefixObj, obj);
  8471. }
  8472. function getSyntaxPreference(name, syntax) {
  8473. if(syntax) {
  8474. var val = prefs.get(syntax + '.' + name);
  8475. if(!_.isUndefined(val)) return val;
  8476. }
  8477. return prefs.get('css.' + name);
  8478. }
  8479. /**
  8480. * Format CSS property according to current syntax dialect
  8481. * @param {String} property
  8482. * @param {String} syntax
  8483. * @returns {String}
  8484. */
  8485. function formatProperty(property, syntax) {
  8486. var ix = property.indexOf(':');
  8487. property = property.substring(0, ix).replace(/\s+$/, '') + getSyntaxPreference('valueSeparator', syntax) + require('utils').trim(property.substring(ix + 1));
  8488. return property.replace(/\s*;\s*$/, getSyntaxPreference('propertyEnd', syntax));
  8489. }
  8490. /**
  8491. * Transforms snippet value if required. For example, this transformation
  8492. * may add <i>!important</i> declaration to CSS property
  8493. * @param {String} snippet
  8494. * @param {Boolean} isImportant
  8495. * @returns {String}
  8496. */
  8497. function transformSnippet(snippet, isImportant, syntax) {
  8498. if(!_.isString(snippet)) snippet = snippet.data;
  8499. if(!isSingleProperty(snippet)) return snippet;
  8500. if(isImportant) {
  8501. if(~snippet.indexOf(';')) {
  8502. snippet = snippet.split(';').join(' !important;');
  8503. } else {
  8504. snippet += ' !important';
  8505. }
  8506. }
  8507. return formatProperty(snippet, syntax);
  8508. // format value separator
  8509. var ix = snippet.indexOf(':');
  8510. snippet = snippet.substring(0, ix).replace(/\s+$/, '') + prefs.get('css.valueSeparator') + require('utils').trim(snippet.substring(ix + 1));
  8511. return snippet;
  8512. }
  8513. /**
  8514. * Helper function that parses comma-separated list of elements into array
  8515. * @param {String} list
  8516. * @returns {Array}
  8517. */
  8518. function parseList(list) {
  8519. var result = _.map((list || '').split(','), require('utils').trim);
  8520. return result.length ? result : null;
  8521. }
  8522. function getProperties(key) {
  8523. var list = prefs.getArray(key);
  8524. _.each(prefs.getArray(key + 'Addon'), function(prop) {
  8525. if(prop.charAt(0) == '-') {
  8526. list = _.without(list, prop.substr(1));
  8527. } else {
  8528. if(prop.charAt(0) == '+') prop = prop.substr(1);
  8529. list.push(prop);
  8530. }
  8531. });
  8532. return list;
  8533. }
  8534. addPrefix('w', {
  8535. prefix: 'webkit',
  8536. supports: getProperties('css.webkitProperties')
  8537. });
  8538. addPrefix('m', {
  8539. prefix: 'moz',
  8540. supports: getProperties('css.mozProperties')
  8541. });
  8542. addPrefix('s', {
  8543. prefix: 'ms',
  8544. supports: getProperties('css.msProperties')
  8545. });
  8546. addPrefix('o', {
  8547. prefix: 'o',
  8548. supports: getProperties('css.oProperties')
  8549. });
  8550. // I think nobody uses it
  8551. // addPrefix('k', {
  8552. // prefix: 'khtml',
  8553. // obsolete: true
  8554. // });
  8555. var cssSyntaxes = ['css', 'less', 'sass', 'scss', 'stylus'];
  8556. /**
  8557. * XXX register resolver
  8558. * @param {TreeNode} node
  8559. * @param {String} syntax
  8560. */
  8561. require('resources').addResolver(function(node, syntax) {
  8562. if(_.include(cssSyntaxes, syntax) && node.isElement()) {
  8563. return module.expandToSnippet(node.abbreviation, syntax);
  8564. }
  8565. return null;
  8566. });
  8567. var ea = require('expandAbbreviation');
  8568. /**
  8569. * For CSS-like syntaxes, we need to handle a special use case. Some editors
  8570. * (like Sublime Text 2) may insert semicolons automatically when user types
  8571. * abbreviation. After expansion, user receives a double semicolon. This
  8572. * handler automatically removes semicolon from generated content in such cases.
  8573. * @param {IEmmetEditor} editor
  8574. * @param {String} syntax
  8575. * @param {String} profile
  8576. */
  8577. ea.addHandler(function(editor, syntax, profile) {
  8578. if(!_.include(cssSyntaxes, syntax)) {
  8579. return false;
  8580. }
  8581. var caretPos = editor.getSelectionRange().end;
  8582. var abbr = ea.findAbbreviation(editor);
  8583. if(abbr) {
  8584. var content = emmet.expandAbbreviation(abbr, syntax, profile);
  8585. if(content) {
  8586. var replaceFrom = caretPos - abbr.length;
  8587. var replaceTo = caretPos;
  8588. if(editor.getContent().charAt(caretPos) == ';' && content.charAt(content.length - 1) == ';') {
  8589. replaceTo++;
  8590. }
  8591. editor.replaceContent(content, replaceFrom, replaceTo);
  8592. return true;
  8593. }
  8594. }
  8595. return false;
  8596. });
  8597. return module = {
  8598. /**
  8599. * Adds vendor prefix
  8600. * @param {String} name One-character prefix name
  8601. * @param {Object} obj Object describing vendor prefix
  8602. * @memberOf cssResolver
  8603. */
  8604. addPrefix: addPrefix,
  8605. /**
  8606. * Check if passed CSS property supports specified vendor prefix
  8607. * @param {String} property
  8608. * @param {String} prefix
  8609. */
  8610. supportsPrefix: hasPrefix,
  8611. /**
  8612. * Returns prefixed version of passed CSS property, only if this
  8613. * property supports such prefix
  8614. * @param {String} property
  8615. * @param {String} prefix
  8616. * @returns
  8617. */
  8618. prefixed: function(property, prefix) {
  8619. return hasPrefix(property, prefix) ? '-' + prefix + '-' + property : property;
  8620. },
  8621. /**
  8622. * Returns list of all registered vendor prefixes
  8623. * @returns {Array}
  8624. */
  8625. listPrefixes: function() {
  8626. return _.map(vendorPrefixes, function(obj) {
  8627. return obj.prefix;
  8628. });
  8629. },
  8630. /**
  8631. * Returns object describing vendor prefix
  8632. * @param {String} name
  8633. * @returns {Object}
  8634. */
  8635. getPrefix: function(name) {
  8636. return vendorPrefixes[name];
  8637. },
  8638. /**
  8639. * Removes prefix object
  8640. * @param {String} name
  8641. */
  8642. removePrefix: function(name) {
  8643. if(name in vendorPrefixes) delete vendorPrefixes[name];
  8644. },
  8645. /**
  8646. * Extract vendor prefixes from abbreviation
  8647. * @param {String} abbr
  8648. * @returns {Object} Object containing array of prefixes and clean
  8649. * abbreviation name
  8650. */
  8651. extractPrefixes: function(abbr) {
  8652. if(abbr.charAt(0) != '-') {
  8653. return {
  8654. property: abbr,
  8655. prefixes: null
  8656. };
  8657. }
  8658. // abbreviation may either contain sequence of one-character prefixes
  8659. // or just dash, meaning that user wants to produce all possible
  8660. // prefixed properties
  8661. var i = 1,
  8662. il = abbr.length,
  8663. ch;
  8664. var prefixes = [];
  8665. while(i < il) {
  8666. ch = abbr.charAt(i);
  8667. if(ch == '-') {
  8668. // end-sequence character found, stop searching
  8669. i++;
  8670. break;
  8671. }
  8672. if(ch in vendorPrefixes) {
  8673. prefixes.push(ch);
  8674. } else {
  8675. // no prefix found, meaning user want to produce all
  8676. // vendor-prefixed properties
  8677. prefixes.length = 0;
  8678. i = 1;
  8679. break;
  8680. }
  8681. i++;
  8682. }
  8683. // reached end of abbreviation and no property name left
  8684. if(i == il - 1) {
  8685. i = 1;
  8686. prefixes.length = 1;
  8687. }
  8688. return {
  8689. property: abbr.substring(i),
  8690. prefixes: prefixes.length ? prefixes : 'all'
  8691. };
  8692. },
  8693. /**
  8694. * Search for value substring in abbreviation
  8695. * @param {String} abbr
  8696. * @returns {String} Value substring
  8697. */
  8698. findValuesInAbbreviation: function(abbr, syntax) {
  8699. syntax = syntax || 'css';
  8700. var i = 0,
  8701. il = abbr.length,
  8702. value = '',
  8703. ch;
  8704. while(i < il) {
  8705. ch = abbr.charAt(i);
  8706. if(isNumeric(ch) || (ch == '-' && isNumeric(abbr.charAt(i + 1)))) {
  8707. value = abbr.substring(i);
  8708. break;
  8709. }
  8710. i++;
  8711. }
  8712. // try to find keywords in abbreviation
  8713. var property = abbr.substring(0, abbr.length - value.length);
  8714. var res = require('resources');
  8715. var keywords = [];
  8716. // try to extract some commonly-used properties
  8717. while(~property.indexOf('-') && !res.findSnippet(syntax, property)) {
  8718. var parts = property.split('-');
  8719. var lastPart = parts.pop();
  8720. if(!isValidKeyword(lastPart)) {
  8721. break;
  8722. }
  8723. keywords.unshift(lastPart);
  8724. property = parts.join('-');
  8725. }
  8726. return keywords.join('-') + value;
  8727. },
  8728. /**
  8729. * Parses values defined in abbreviations
  8730. * @param {String} abbrValues Values part of abbreviations (can be
  8731. * extracted with <code>findValuesInAbbreviation</code>)
  8732. * @returns {Array}
  8733. */
  8734. parseValues: function(abbrValues) {
  8735. var valueStack = '';
  8736. var values = [];
  8737. var i = 0,
  8738. il = abbrValues.length,
  8739. ch, nextCh;
  8740. while(i < il) {
  8741. ch = abbrValues.charAt(i);
  8742. if(ch == '-' && valueStack) {
  8743. // next value found
  8744. values.push(valueStack);
  8745. valueStack = '';
  8746. i++;
  8747. continue;
  8748. }
  8749. valueStack += ch;
  8750. i++;
  8751. nextCh = abbrValues.charAt(i);
  8752. if(ch != '-' && !isNumeric(ch) && (isNumeric(nextCh) || nextCh == '-')) {
  8753. if(isValidKeyword(valueStack)) {
  8754. i++;
  8755. }
  8756. values.push(valueStack);
  8757. valueStack = '';
  8758. }
  8759. }
  8760. if(valueStack) {
  8761. values.push(valueStack);
  8762. }
  8763. return _.map(values, getKeyword);
  8764. },
  8765. /**
  8766. * Extracts values from abbreviation
  8767. * @param {String} abbr
  8768. * @returns {Object} Object containing array of values and clean
  8769. * abbreviation name
  8770. */
  8771. extractValues: function(abbr) {
  8772. // search for value start
  8773. var abbrValues = this.findValuesInAbbreviation(abbr);
  8774. if(!abbrValues) {
  8775. return {
  8776. property: abbr,
  8777. values: null
  8778. };
  8779. }
  8780. return {
  8781. property: abbr.substring(0, abbr.length - abbrValues.length).replace(/-$/, ''),
  8782. values: this.parseValues(abbrValues)
  8783. };
  8784. },
  8785. /**
  8786. * Normalizes value, defined in abbreviation.
  8787. * @param {String} value
  8788. * @param {String} property
  8789. * @returns {String}
  8790. */
  8791. normalizeValue: function(value, property) {
  8792. property = (property || '').toLowerCase();
  8793. var unitlessProps = prefs.getArray('css.unitlessProperties');
  8794. return value.replace(/^(\-?[0-9\.]+)([a-z]*)$/, function(str, val, unit) {
  8795. if(!unit && (val == '0' || _.include(unitlessProps, property))) return val;
  8796. if(!unit) return val + prefs.get(~val.indexOf('.') ? 'css.floatUnit' : 'css.intUnit');
  8797. return val + getUnit(unit);
  8798. });
  8799. },
  8800. /**
  8801. * Expands abbreviation into a snippet
  8802. * @param {String} abbr Abbreviation name to expand
  8803. * @param {String} value Abbreviation value
  8804. * @param {String} syntax Currect syntax or dialect. Default is 'css'
  8805. * @returns {Object} Array of CSS properties and values or predefined
  8806. * snippet (string or element)
  8807. */
  8808. expand: function(abbr, value, syntax) {
  8809. syntax = syntax || 'css';
  8810. var resources = require('resources');
  8811. var autoInsertPrefixes = prefs.get('css.autoInsertVendorPrefixes');
  8812. // check if snippet should be transformed to !important
  8813. var isImportant;
  8814. if(isImportant = /^(.+)\!$/.test(abbr)) {
  8815. abbr = RegExp.$1;
  8816. }
  8817. // check if we have abbreviated resource
  8818. var snippet = resources.findSnippet(syntax, abbr);
  8819. if(snippet && !autoInsertPrefixes) {
  8820. return transformSnippet(snippet, isImportant, syntax);
  8821. }
  8822. // no abbreviated resource, parse abbreviation
  8823. var prefixData = this.extractPrefixes(abbr);
  8824. var valuesData = this.extractValues(prefixData.property);
  8825. var abbrData = _.extend(prefixData, valuesData);
  8826. snippet = resources.findSnippet(syntax, abbrData.property);
  8827. // fallback to some old snippets like m:a
  8828. if(!snippet && ~abbrData.property.indexOf(':')) {
  8829. var parts = abbrData.property.split(':');
  8830. var propertyName = parts.shift();
  8831. snippet = resources.findSnippet(syntax, propertyName) || propertyName;
  8832. abbrData.values = this.parseValues(parts.join(':'));
  8833. }
  8834. if(!snippet) {
  8835. snippet = abbrData.property + ':' + defaultValue;
  8836. } else if(!_.isString(snippet)) {
  8837. snippet = snippet.data;
  8838. }
  8839. if(!isSingleProperty(snippet)) {
  8840. return snippet;
  8841. }
  8842. var snippetObj = splitSnippet(snippet);
  8843. var result = [];
  8844. if(!value && abbrData.values) {
  8845. value = _.map(abbrData.values, function(val) {
  8846. return this.normalizeValue(val, snippetObj.name);
  8847. }, this).join(' ') + ';';
  8848. }
  8849. snippetObj.value = value || snippetObj.value;
  8850. var prefixes = abbrData.prefixes == 'all' || (!abbrData.prefixes && autoInsertPrefixes) ? findPrefixes(snippetObj.name, autoInsertPrefixes && abbrData.prefixes != 'all') : abbrData.prefixes;
  8851. _.each(prefixes, function(p) {
  8852. if(p in vendorPrefixes) {
  8853. result.push(transformSnippet(
  8854. vendorPrefixes[p].transformName(snippetObj.name) + ':' + snippetObj.value, isImportant, syntax));
  8855. }
  8856. });
  8857. // put the original property
  8858. result.push(transformSnippet(snippetObj.name + ':' + snippetObj.value, isImportant, syntax));
  8859. return result;
  8860. },
  8861. /**
  8862. * Same as <code>expand</code> method but transforms output into a
  8863. * Emmet snippet
  8864. * @param {String} abbr
  8865. * @param {String} syntax
  8866. * @returns {String}
  8867. */
  8868. expandToSnippet: function(abbr, syntax) {
  8869. var snippet = this.expand(abbr, null, syntax);
  8870. if(_.isArray(snippet)) {
  8871. return snippet.join('\n');
  8872. }
  8873. if(!_.isString(snippet)) return snippet.data;
  8874. return String(snippet);
  8875. },
  8876. getSyntaxPreference: getSyntaxPreference
  8877. };
  8878. });
  8879. /**
  8880. * 'Expand Abbreviation' handler that parses gradient definition from under
  8881. * cursor and updates CSS rule with vendor-prefixed values.
  8882. *
  8883. * @memberOf __cssGradientHandlerDefine
  8884. * @param {Function} require
  8885. * @param {Underscore} _
  8886. */
  8887. emmet.define('cssGradient', function(require, _) {
  8888. var defaultLinearDirections = ['top', 'to bottom', '0deg']; /** Back-reference to current module */
  8889. var module = null;
  8890. var cssSyntaxes = ['css', 'less', 'sass', 'scss', 'stylus', 'styl'];
  8891. var reDeg = /\d+deg/i;
  8892. var reKeyword = /top|bottom|left|right/i;
  8893. // XXX define preferences
  8894. /** @type preferences */
  8895. var prefs = require('preferences');
  8896. prefs.define('css.gradient.prefixes', 'webkit, moz, o', 'A comma-separated list of vendor-prefixes for which values should ' + 'be generated.');
  8897. prefs.define('css.gradient.oldWebkit', true, 'Generate gradient definition for old Webkit implementations');
  8898. prefs.define('css.gradient.omitDefaultDirection', true, 'Do not output default direction definition in generated gradients.');
  8899. prefs.define('css.gradient.defaultProperty', 'background-image', 'When gradient expanded outside CSS value context, it will produce ' + 'properties with this name.');
  8900. prefs.define('css.gradient.fallback', false, 'With this option enabled, CSS gradient generator will produce ' + '<code>background-color</code> property with gradient first color ' + 'as fallback for old browsers.');
  8901. function normalizeSpace(str) {
  8902. return require('utils').trim(str).replace(/\s+/g, ' ');
  8903. }
  8904. /**
  8905. * Parses linear gradient definition
  8906. * @param {String}
  8907. */
  8908. function parseLinearGradient(gradient) {
  8909. var direction = defaultLinearDirections[0];
  8910. // extract tokens
  8911. /** @type StringStream */
  8912. var stream = require('stringStream').create(require('utils').trim(gradient));
  8913. var colorStops = [],
  8914. ch;
  8915. while(ch = stream.next()) {
  8916. if(stream.peek() == ',') {
  8917. colorStops.push(stream.current());
  8918. stream.next();
  8919. stream.eatSpace();
  8920. stream.start = stream.pos;
  8921. } else if(ch == '(') { // color definition, like 'rgb(0,0,0)'
  8922. stream.skipTo(')');
  8923. }
  8924. }
  8925. // add last token
  8926. colorStops.push(stream.current());
  8927. colorStops = _.compact(_.map(colorStops, normalizeSpace));
  8928. if(!colorStops.length) return null;
  8929. // let's see if the first color stop is actually a direction
  8930. if(reDeg.test(colorStops[0]) || reKeyword.test(colorStops[0])) {
  8931. direction = colorStops.shift();
  8932. }
  8933. return {
  8934. type: 'linear',
  8935. direction: direction,
  8936. colorStops: _.map(colorStops, parseColorStop)
  8937. };
  8938. }
  8939. /**
  8940. * Parses color stop definition
  8941. * @param {String} colorStop
  8942. * @returns {Object}
  8943. */
  8944. function parseColorStop(colorStop) {
  8945. colorStop = normalizeSpace(colorStop);
  8946. // find color declaration
  8947. // first, try complex color declaration, like rgb(0,0,0)
  8948. var color = null;
  8949. colorStop = colorStop.replace(/^(\w+\(.+?\))\s*/, function(str, c) {
  8950. color = c;
  8951. return '';
  8952. });
  8953. if(!color) {
  8954. // try simple declaration, like yellow, #fco, #ffffff, etc.
  8955. var parts = colorStop.split(' ');
  8956. color = parts[0];
  8957. colorStop = parts[1] || '';
  8958. }
  8959. var result = {
  8960. color: color
  8961. };
  8962. if(colorStop) {
  8963. // there's position in color stop definition
  8964. colorStop.replace(/^(\-?[\d\.]+)([a-z%]+)?$/, function(str, pos, unit) {
  8965. result.position = pos;
  8966. if(~pos.indexOf('.')) {
  8967. unit = '';
  8968. } else if(!unit) {
  8969. unit = '%';
  8970. }
  8971. if(unit) result.unit = unit;
  8972. });
  8973. }
  8974. return result;
  8975. }
  8976. /**
  8977. * Fills-out implied positions in color-stops. This function is useful for
  8978. * old Webkit gradient definitions
  8979. */
  8980. function fillImpliedPositions(colorStops) {
  8981. var from = 0;
  8982. _.each(colorStops, function(cs, i) {
  8983. // make sure that first and last positions are defined
  8984. if(!i) return cs.position = cs.position || 0;
  8985. if(i == colorStops.length - 1 && !('position' in cs)) cs.position = 1;
  8986. if('position' in cs) {
  8987. var start = colorStops[from].position || 0;
  8988. var step = (cs.position - start) / (i - from);
  8989. _.each(colorStops.slice(from, i), function(cs2, j) {
  8990. cs2.position = start + step * j;
  8991. });
  8992. from = i;
  8993. }
  8994. });
  8995. }
  8996. /**
  8997. * Returns textual version of direction expressed in degrees
  8998. * @param {String} direction
  8999. * @returns {String}
  9000. */
  9001. function textualDirection(direction) {
  9002. var angle = parseFloat(direction);
  9003. if(!_.isNaN(angle)) {
  9004. switch(angle % 360) {
  9005. case 0:
  9006. return 'left';
  9007. case 90:
  9008. return 'bottom';
  9009. case 180:
  9010. return 'right';
  9011. case 240:
  9012. return 'top';
  9013. }
  9014. }
  9015. return direction;
  9016. }
  9017. /**
  9018. * Creates direction definition for old Webkit gradients
  9019. * @param {String} direction
  9020. * @returns {String}
  9021. */
  9022. function oldWebkitDirection(direction) {
  9023. direction = textualDirection(direction);
  9024. if(reDeg.test(direction)) throw "The direction is an angle that can’t be converted.";
  9025. var v = function(pos) {
  9026. return ~direction.indexOf(pos) ? '100%' : '0';
  9027. };
  9028. return v('right') + ' ' + v('bottom') + ', ' + v('left') + ' ' + v('top');
  9029. }
  9030. function getPrefixedNames(name) {
  9031. var prefixes = prefs.getArray('css.gradient.prefixes');
  9032. var names = _.map(prefixes, function(p) {
  9033. return '-' + p + '-' + name;
  9034. });
  9035. names.push(name);
  9036. return names;
  9037. }
  9038. /**
  9039. * Returns list of CSS properties with gradient
  9040. * @param {Object} gradient
  9041. * @param {String} propertyName Original CSS property name
  9042. * @returns {Array}
  9043. */
  9044. function getPropertiesForGradient(gradient, propertyName) {
  9045. var props = [];
  9046. var css = require('cssResolver');
  9047. if(prefs.get('css.gradient.fallback') && ~propertyName.toLowerCase().indexOf('background')) {
  9048. props.push({
  9049. name: 'background-color',
  9050. value: '${1:' + gradient.colorStops[0].color + '}'
  9051. });
  9052. }
  9053. _.each(prefs.getArray('css.gradient.prefixes'), function(prefix) {
  9054. var name = css.prefixed(propertyName, prefix);
  9055. if(prefix == 'webkit' && prefs.get('css.gradient.oldWebkit')) {
  9056. try {
  9057. props.push({
  9058. name: name,
  9059. value: module.oldWebkitLinearGradient(gradient)
  9060. });
  9061. } catch(e) {}
  9062. }
  9063. props.push({
  9064. name: name,
  9065. value: module.toString(gradient, prefix)
  9066. });
  9067. });
  9068. return props.sort(function(a, b) {
  9069. return b.name.length - a.name.length;
  9070. });
  9071. }
  9072. /**
  9073. * Pastes gradient definition into CSS rule with correct vendor-prefixes
  9074. * @param {EditElement} property Matched CSS property
  9075. * @param {Object} gradient Parsed gradient
  9076. * @param {Range} valueRange If passed, only this range within property
  9077. * value will be replaced with gradient. Otherwise, full value will be
  9078. * replaced
  9079. */
  9080. function pasteGradient(property, gradient, valueRange) {
  9081. var rule = property.parent;
  9082. var utils = require('utils');
  9083. // first, remove all properties within CSS rule with the same name and
  9084. // gradient definition
  9085. _.each(rule.getAll(getPrefixedNames(property.name())), function(item) {
  9086. if(item != property && /gradient/i.test(item.value())) {
  9087. rule.remove(item);
  9088. }
  9089. });
  9090. var value = property.value();
  9091. if(!valueRange) valueRange = require('range').create(0, property.value());
  9092. var val = function(v) {
  9093. return utils.replaceSubstring(value, v, valueRange);
  9094. };
  9095. // put vanilla-clean gradient definition into current rule
  9096. property.value(val(module.toString(gradient)) + '${2}');
  9097. // create list of properties to insert
  9098. var propsToInsert = getPropertiesForGradient(gradient, property.name());
  9099. // put vendor-prefixed definitions before current rule
  9100. _.each(propsToInsert, function(prop) {
  9101. rule.add(prop.name, prop.value, rule.indexOf(property));
  9102. });
  9103. }
  9104. /**
  9105. * Search for gradient definition inside CSS property value
  9106. */
  9107. function findGradient(cssProp) {
  9108. var value = cssProp.value();
  9109. var gradient = null;
  9110. var matchedPart = _.find(cssProp.valueParts(), function(part) {
  9111. return gradient = module.parse(part.substring(value));
  9112. });
  9113. if(matchedPart && gradient) {
  9114. return {
  9115. gradient: gradient,
  9116. valueRange: matchedPart
  9117. };
  9118. }
  9119. return null;
  9120. }
  9121. /**
  9122. * Tries to expand gradient outside CSS value
  9123. * @param {IEmmetEditor} editor
  9124. * @param {String} syntax
  9125. */
  9126. function expandGradientOutsideValue(editor, syntax) {
  9127. var propertyName = prefs.get('css.gradient.defaultProperty');
  9128. if(!propertyName) return false;
  9129. // assuming that gradient definition is written on new line,
  9130. // do a simplified parsing
  9131. var content = String(editor.getContent()); /** @type Range */
  9132. var lineRange = require('range').create(editor.getCurrentLineRange());
  9133. // get line content and adjust range with padding
  9134. var line = lineRange.substring(content).replace(/^\s+/, function(pad) {
  9135. lineRange.start += pad.length;
  9136. return '';
  9137. }).replace(/\s+$/, function(pad) {
  9138. lineRange.end -= pad.length;
  9139. return '';
  9140. });
  9141. var css = require('cssResolver');
  9142. var gradient = module.parse(line);
  9143. if(gradient) {
  9144. var props = getPropertiesForGradient(gradient, propertyName);
  9145. props.push({
  9146. name: propertyName,
  9147. value: module.toString(gradient) + '${2}'
  9148. });
  9149. var sep = css.getSyntaxPreference('valueSeparator', syntax);
  9150. var end = css.getSyntaxPreference('propertyEnd', syntax);
  9151. props = _.map(props, function(item) {
  9152. return item.name + sep + item.value + end;
  9153. });
  9154. editor.replaceContent(props.join('\n'), lineRange.start, lineRange.end);
  9155. return true;
  9156. }
  9157. return false;
  9158. }
  9159. /**
  9160. * Search for gradient definition inside CSS value under cursor
  9161. * @param {String} content
  9162. * @param {Number} pos
  9163. * @returns {Object}
  9164. */
  9165. function findGradientFromPosition(content, pos) {
  9166. var cssProp = null; /** @type EditContainer */
  9167. var cssRule = require('cssEditTree').parseFromPosition(content, pos, true);
  9168. if(cssRule) {
  9169. cssProp = cssRule.itemFromPosition(pos, true);
  9170. if(!cssProp) {
  9171. // in case user just started writing CSS property
  9172. // and didn't include semicolon–try another approach
  9173. cssProp = _.find(cssRule.list(), function(elem) {
  9174. return elem.range(true).end == pos;
  9175. });
  9176. }
  9177. }
  9178. return {
  9179. rule: cssRule,
  9180. property: cssProp
  9181. };
  9182. }
  9183. // XXX register expand abbreviation handler
  9184. /**
  9185. * @param {IEmmetEditor} editor
  9186. * @param {String} syntax
  9187. * @param {String} profile
  9188. */
  9189. require('expandAbbreviation').addHandler(function(editor, syntax, profile) {
  9190. var info = require('editorUtils').outputInfo(editor, syntax, profile);
  9191. if(!_.include(cssSyntaxes, info.syntax)) return false;
  9192. // let's see if we are expanding gradient definition
  9193. var caret = editor.getCaretPos();
  9194. var content = info.content;
  9195. var css = findGradientFromPosition(content, caret);
  9196. if(css.property) {
  9197. // make sure that caret is inside property value with gradient
  9198. // definition
  9199. var g = findGradient(css.property);
  9200. if(g) {
  9201. var ruleStart = css.rule.options.offset || 0;
  9202. var ruleEnd = ruleStart + css.rule.toString().length;
  9203. // Handle special case:
  9204. // user wrote gradient definition between existing CSS
  9205. // properties and did not finished it with semicolon.
  9206. // In this case, we have semicolon right after gradient
  9207. // definition and re-parse rule again
  9208. if(/[\n\r]/.test(css.property.value())) {
  9209. // insert semicolon at the end of gradient definition
  9210. var insertPos = css.property.valueRange(true).start + g.valueRange.end;
  9211. content = require('utils').replaceSubstring(content, ';', insertPos);
  9212. var newCss = findGradientFromPosition(content, caret);
  9213. if(newCss.property) {
  9214. g = findGradient(newCss.property);
  9215. css = newCss;
  9216. }
  9217. }
  9218. // make sure current property has terminating semicolon
  9219. css.property.end(';');
  9220. pasteGradient(css.property, g.gradient, g.valueRange);
  9221. editor.replaceContent(css.rule.toString(), ruleStart, ruleEnd, true);
  9222. return true;
  9223. }
  9224. }
  9225. return expandGradientOutsideValue(editor, syntax);
  9226. });
  9227. // XXX register "Reflect CSS Value" action delegate
  9228. /**
  9229. * @param {EditElement} property
  9230. */
  9231. require('reflectCSSValue').addHandler(function(property) {
  9232. var utils = require('utils');
  9233. var g = findGradient(property);
  9234. if(!g) return false;
  9235. var value = property.value();
  9236. var val = function(v) {
  9237. return utils.replaceSubstring(value, v, g.valueRange);
  9238. };
  9239. // reflect value for properties with the same name
  9240. _.each(property.parent.getAll(getPrefixedNames(property.name())), function(prop) {
  9241. if(prop === property) return;
  9242. // check if property value starts with gradient definition
  9243. var m = prop.value().match(/^\s*(\-([a-z]+)\-)?linear\-gradient/);
  9244. if(m) {
  9245. prop.value(val(module.toString(g.gradient, m[2] || '')));
  9246. } else if(m = prop.value().match(/\s*\-webkit\-gradient/)) {
  9247. // old webkit gradient definition
  9248. prop.value(val(module.oldWebkitLinearGradient(g.gradient)));
  9249. }
  9250. });
  9251. return true;
  9252. });
  9253. return module = {
  9254. /**
  9255. * Parses gradient definition
  9256. * @param {String} gradient
  9257. * @returns {Object}
  9258. */
  9259. parse: function(gradient) {
  9260. var result = null;
  9261. require('utils').trim(gradient).replace(/^([\w\-]+)\((.+?)\)$/, function(str, type, definition) {
  9262. // remove vendor prefix
  9263. type = type.toLowerCase().replace(/^\-[a-z]+\-/, '');
  9264. if(type == 'linear-gradient' || type == 'lg') {
  9265. result = parseLinearGradient(definition);
  9266. return '';
  9267. }
  9268. return str;
  9269. });
  9270. return result;
  9271. },
  9272. /**
  9273. * Produces linear gradient definition used in early Webkit
  9274. * implementations
  9275. * @param {Object} gradient Parsed gradient
  9276. * @returns {String}
  9277. */
  9278. oldWebkitLinearGradient: function(gradient) {
  9279. if(_.isString(gradient)) gradient = this.parse(gradient);
  9280. if(!gradient) return null;
  9281. var colorStops = _.map(gradient.colorStops, _.clone);
  9282. // normalize color-stops position
  9283. _.each(colorStops, function(cs) {
  9284. if(!('position' in cs)) // implied position
  9285. return;
  9286. if(~cs.position.indexOf('.') || cs.unit == '%') {
  9287. cs.position = parseFloat(cs.position) / (cs.unit == '%' ? 100 : 1);
  9288. } else {
  9289. throw "Can't convert color stop '" + (cs.position + (cs.unit || '')) + "'";
  9290. }
  9291. });
  9292. fillImpliedPositions(colorStops);
  9293. // transform color-stops into string representation
  9294. colorStops = _.map(colorStops, function(cs, i) {
  9295. if(!cs.position && !i) return 'from(' + cs.color + ')';
  9296. if(cs.position == 1 && i == colorStops.length - 1) return 'to(' + cs.color + ')';
  9297. return 'color-stop(' + (cs.position.toFixed(2).replace(/\.?0+$/, '')) + ', ' + cs.color + ')';
  9298. });
  9299. return '-webkit-gradient(linear, ' + oldWebkitDirection(gradient.direction) + ', ' + colorStops.join(', ') + ')';
  9300. },
  9301. /**
  9302. * Returns string representation of parsed gradient
  9303. * @param {Object} gradient Parsed gradient
  9304. * @param {String} prefix Vendor prefix
  9305. * @returns {String}
  9306. */
  9307. toString: function(gradient, prefix) {
  9308. if(gradient.type == 'linear') {
  9309. var fn = (prefix ? '-' + prefix + '-' : '') + 'linear-gradient';
  9310. // transform color-stops
  9311. var colorStops = _.map(gradient.colorStops, function(cs) {
  9312. return cs.color + ('position' in cs ? ' ' + cs.position + (cs.unit || '') : '');
  9313. });
  9314. if(gradient.direction && (!prefs.get('css.gradient.omitDefaultDirection') || !_.include(defaultLinearDirections, gradient.direction))) {
  9315. colorStops.unshift(gradient.direction);
  9316. }
  9317. return fn + '(' + colorStops.join(', ') + ')';
  9318. }
  9319. }
  9320. };
  9321. });
  9322. /**
  9323. * Module adds support for generators: a regexp-based abbreviation resolver
  9324. * that can produce custom output.
  9325. * @param {Function} require
  9326. * @param {Underscore} _
  9327. */
  9328. emmet.exec(function(require, _) { /** @type HandlerList */
  9329. var generators = require('handlerList').create();
  9330. var resources = require('resources');
  9331. _.extend(resources, {
  9332. /**
  9333. * Add generator. A generator function <code>fn</code> will be called
  9334. * only if current abbreviation matches <code>regexp</code> regular
  9335. * expression and this function should return <code>null</code> if
  9336. * abbreviation cannot be resolved
  9337. * @param {RegExp} regexp Regular expression for abbreviation element name
  9338. * @param {Function} fn Resolver function
  9339. * @param {Object} options Options list as described in
  9340. * {@link HandlerList#add()} method
  9341. */
  9342. addGenerator: function(regexp, fn, options) {
  9343. if(_.isString(regexp)) regexp = new RegExp(regexp);
  9344. generators.add(function(node, syntax) {
  9345. var m;
  9346. if((m = regexp.exec(node.name()))) {
  9347. return fn(m, node, syntax);
  9348. }
  9349. return null;
  9350. }, options);
  9351. }
  9352. });
  9353. resources.addResolver(function(node, syntax) {
  9354. return generators.exec(null, _.toArray(arguments));
  9355. });
  9356. });
  9357. /**
  9358. * Module for resolving tag names: returns best matched tag name for child
  9359. * element based on passed parent's tag name. Also provides utility function
  9360. * for element type detection (inline, block-level, empty)
  9361. * @param {Function} require
  9362. * @param {Underscore} _
  9363. */
  9364. emmet.define('tagName', function(require, _) {
  9365. var elementTypes = {
  9366. empty: 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command'.split(','),
  9367. 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(','),
  9368. 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(',')
  9369. };
  9370. var elementMap = {
  9371. 'p': 'span',
  9372. 'ul': 'li',
  9373. 'ol': 'li',
  9374. 'table': 'tr',
  9375. 'tr': 'td',
  9376. 'tbody': 'tr',
  9377. 'thead': 'tr',
  9378. 'tfoot': 'tr',
  9379. 'colgroup': 'col',
  9380. 'select': 'option',
  9381. 'optgroup': 'option',
  9382. 'audio': 'source',
  9383. 'video': 'source',
  9384. 'object': 'param',
  9385. 'map': 'area'
  9386. };
  9387. return {
  9388. /**
  9389. * Returns best matched child element name for passed parent's
  9390. * tag name
  9391. * @param {String} name
  9392. * @returns {String}
  9393. * @memberOf tagName
  9394. */
  9395. resolve: function(name) {
  9396. name = (name || '').toLowerCase();
  9397. if(name in elementMap) return this.getMapping(name);
  9398. if(this.isInlineLevel(name)) return 'span';
  9399. return 'div';
  9400. },
  9401. /**
  9402. * Returns mapped child element name for passed parent's name
  9403. * @param {String} name
  9404. * @returns {String}
  9405. */
  9406. getMapping: function(name) {
  9407. return elementMap[name.toLowerCase()];
  9408. },
  9409. /**
  9410. * Check if passed element name belongs to inline-level element
  9411. * @param {String} name
  9412. * @returns {Boolean}
  9413. */
  9414. isInlineLevel: function(name) {
  9415. return this.isTypeOf(name, 'inlineLevel');
  9416. },
  9417. /**
  9418. * Check if passed element belongs to block-level element.
  9419. * For better matching of unknown elements (for XML, for example),
  9420. * you should use <code>!this.isInlineLevel(name)</code>
  9421. * @returns {Boolean}
  9422. */
  9423. isBlockLevel: function(name) {
  9424. return this.isTypeOf(name, 'blockLevel');
  9425. },
  9426. /**
  9427. * Check if passed element is void (i.e. should not have closing tag).
  9428. * @returns {Boolean}
  9429. */
  9430. isEmptyElement: function(name) {
  9431. return this.isTypeOf(name, 'empty');
  9432. },
  9433. /**
  9434. * Generic function for testing if element name belongs to specified
  9435. * elements collection
  9436. * @param {String} name Element name
  9437. * @param {String} type Collection name
  9438. * @returns {Boolean}
  9439. */
  9440. isTypeOf: function(name, type) {
  9441. return _.include(elementTypes[type], name);
  9442. },
  9443. /**
  9444. * Adds new parent–child mapping
  9445. * @param {String} parent
  9446. * @param {String} child
  9447. */
  9448. addMapping: function(parent, child) {
  9449. elementMap[parent] = child;
  9450. },
  9451. /**
  9452. * Removes parent-child mapping
  9453. */
  9454. removeMapping: function(parent) {
  9455. if(parent in elementMap) delete elementMap[parent];
  9456. },
  9457. /**
  9458. * Adds new element into collection
  9459. * @param {String} name Element name
  9460. * @param {String} collection Collection name
  9461. */
  9462. addElementToCollection: function(name, collection) {
  9463. if(!elementTypes[collection]) elementTypes[collection] = [];
  9464. var col = this.getCollection(collection);
  9465. if(!_.include(col, name)) col.push(name);
  9466. },
  9467. /**
  9468. * Removes element name from specified collection
  9469. * @param {String} name Element name
  9470. * @param {String} collection Collection name
  9471. * @returns
  9472. */
  9473. removeElementFromCollection: function(name, collection) {
  9474. if(collection in elementTypes) {
  9475. elementTypes[collection] = _.without(this.getCollection(collection), name);
  9476. }
  9477. },
  9478. /**
  9479. * Returns elements name collection
  9480. * @param {String} name Collection name
  9481. * @returns {Array}
  9482. */
  9483. getCollection: function(name) {
  9484. return elementTypes[name];
  9485. }
  9486. };
  9487. });
  9488. /**
  9489. * Filter for aiding of writing elements with complex class names as described
  9490. * in Yandex's BEM (Block, Element, Modifier) methodology. This filter will
  9491. * automatically inherit block and element names from parent elements and insert
  9492. * them into child element classes
  9493. * @memberOf __bemFilterDefine
  9494. * @constructor
  9495. * @param {Function} require
  9496. * @param {Underscore} _
  9497. */
  9498. emmet.exec(function(require, _) {
  9499. var prefs = require('preferences');
  9500. prefs.define('bem.elementSeparator', '__', 'Class name’s element separator.');
  9501. prefs.define('bem.modifierSeparator', '_', 'Class name’s modifier separator.');
  9502. prefs.define('bem.shortElementPrefix', '-', 'Symbol for describing short “block-element” notation. Class names ' + 'prefixed with this symbol will be treated as element name for parent‘s ' + 'block name. Each symbol instance traverses one level up in parsed ' + 'tree for block name lookup. Empty value will disable short notation.');
  9503. var shouldRunHtmlFilter = false;
  9504. function getSeparators() {
  9505. return {
  9506. element: prefs.get('bem.elementSeparator'),
  9507. modifier: prefs.get('bem.modifierSeparator')
  9508. };
  9509. }
  9510. /**
  9511. * @param {AbbreviationNode} item
  9512. */
  9513. function bemParse(item) {
  9514. if(require('abbreviationUtils').isSnippet(item)) return item;
  9515. // save BEM stuff in cache for faster lookups
  9516. item.__bem = {
  9517. block: '',
  9518. element: '',
  9519. modifier: ''
  9520. };
  9521. var classNames = normalizeClassName(item.attribute('class')).split(' ');
  9522. // guess best match for block name
  9523. var reBlockName = /^[a-z]\-/i;
  9524. item.__bem.block = _.find(classNames, function(name) {
  9525. return reBlockName.test(name);
  9526. });
  9527. // guessing doesn't worked, pick first class name as block name
  9528. if(!item.__bem.block) {
  9529. reBlockName = /^[a-z]/i;
  9530. item.__bem.block = _.find(classNames, function(name) {
  9531. return reBlockName.test(name);
  9532. }) || '';
  9533. }
  9534. classNames = _.chain(classNames).map(function(name) {
  9535. return processClassName(name, item);
  9536. }).flatten().uniq().value().join(' ');
  9537. if(classNames) item.attribute('class', classNames);
  9538. return item;
  9539. }
  9540. /**
  9541. * @param {String} className
  9542. * @returns {String}
  9543. */
  9544. function normalizeClassName(className) {
  9545. var utils = require('utils');
  9546. className = (' ' + (className || '') + ' ').replace(/\s+/g, ' ');
  9547. var shortSymbol = prefs.get('bem.shortElementPrefix');
  9548. if(shortSymbol) {
  9549. var re = new RegExp('\\s(' + utils.escapeForRegexp(shortSymbol) + '+)', 'g');
  9550. className = className.replace(re, function(str, p1) {
  9551. return ' ' + utils.repeatString(getSeparators().element, p1.length);
  9552. });
  9553. }
  9554. return utils.trim(className);
  9555. }
  9556. /**
  9557. * Processes class name
  9558. * @param {String} name Class name item to process
  9559. * @param {AbbreviationNode} item Host node for provided class name
  9560. * @returns Processed class name. May return <code>Array</code> of
  9561. * class names
  9562. */
  9563. function processClassName(name, item) {
  9564. name = transformClassName(name, item, 'element');
  9565. name = transformClassName(name, item, 'modifier');
  9566. // expand class name
  9567. // possible values:
  9568. // * block__element
  9569. // * block__element_modifier
  9570. // * block__element_modifier1_modifier2
  9571. // * block_modifier
  9572. var block = '',
  9573. element = '',
  9574. modifier = '';
  9575. var separators = getSeparators();
  9576. if(~name.indexOf(separators.element)) {
  9577. var blockElem = name.split(separators.element);
  9578. var elemModifiers = blockElem[1].split(separators.modifier);
  9579. block = blockElem[0];
  9580. element = elemModifiers.shift();
  9581. modifier = elemModifiers.join(separators.modifier);
  9582. } else if(~name.indexOf(separators.modifier)) {
  9583. var blockModifiers = name.split(separators.modifier);
  9584. block = blockModifiers.shift();
  9585. modifier = blockModifiers.join(separators.modifier);
  9586. }
  9587. if(block || element || modifier) {
  9588. if(!block) {
  9589. block = item.__bem.block;
  9590. }
  9591. // inherit parent bem element, if exists
  9592. // if (item.parent && item.parent.__bem && item.parent.__bem.element)
  9593. // element = item.parent.__bem.element + separators.element + element;
  9594. // produce multiple classes
  9595. var prefix = block;
  9596. var result = [];
  9597. if(element) {
  9598. prefix += separators.element + element;
  9599. result.push(prefix);
  9600. } else {
  9601. result.push(prefix);
  9602. }
  9603. if(modifier) {
  9604. result.push(prefix + separators.modifier + modifier);
  9605. }
  9606. item.__bem.block = block;
  9607. item.__bem.element = element;
  9608. item.__bem.modifier = modifier;
  9609. return result;
  9610. }
  9611. // ...otherwise, return processed or original class name
  9612. return name;
  9613. }
  9614. /**
  9615. * Low-level function to transform user-typed class name into full BEM class
  9616. * @param {String} name Class name item to process
  9617. * @param {AbbreviationNode} item Host node for provided class name
  9618. * @param {String} entityType Type of entity to be tried to transform
  9619. * ('element' or 'modifier')
  9620. * @returns {String} Processed class name or original one if it can't be
  9621. * transformed
  9622. */
  9623. function transformClassName(name, item, entityType) {
  9624. var separators = getSeparators();
  9625. var reSep = new RegExp('^(' + separators[entityType] + ')+', 'g');
  9626. if(reSep.test(name)) {
  9627. var depth = 0; // parent lookup depth
  9628. var cleanName = name.replace(reSep, function(str, p1) {
  9629. depth = str.length / separators[entityType].length;
  9630. return '';
  9631. });
  9632. // find donor element
  9633. var donor = item;
  9634. while(donor.parent && depth--) {
  9635. donor = donor.parent;
  9636. }
  9637. if(!donor || !donor.__bem) donor = item;
  9638. if(donor && donor.__bem) {
  9639. var prefix = donor.__bem.block;
  9640. // decide if we should inherit element name
  9641. // if (entityType == 'element') {
  9642. // var curElem = cleanName.split(separators.modifier, 1)[0];
  9643. // if (donor.__bem.element && donor.__bem.element != curElem)
  9644. // prefix += separators.element + donor.__bem.element;
  9645. // }
  9646. if(entityType == 'modifier' && donor.__bem.element) prefix += separators.element + donor.__bem.element;
  9647. return prefix + separators[entityType] + cleanName;
  9648. }
  9649. }
  9650. return name;
  9651. }
  9652. /**
  9653. * Recursive function for processing tags, which extends class names
  9654. * according to BEM specs: http://bem.github.com/bem-method/pages/beginning/beginning.ru.html
  9655. * <br><br>
  9656. * It does several things:<br>
  9657. * <ul>
  9658. * <li>Expands complex class name (according to BEM symbol semantics):
  9659. * .block__elem_modifier → .block.block__elem.block__elem_modifier
  9660. * </li>
  9661. * <li>Inherits block name on child elements:
  9662. * .b-block > .__el > .__el → .b-block > .b-block__el > .b-block__el__el
  9663. * </li>
  9664. * <li>Treats first dash symbol as '__'</li>
  9665. * <li>Double underscore (or typographic '–') is also treated as an element
  9666. * level lookup, e.g. ____el will search for element definition in parent’s
  9667. * parent element:
  9668. * .b-block > .__el1 > .____el2 → .b-block > .b-block__el1 > .b-block__el2
  9669. * </li>
  9670. * </ul>
  9671. *
  9672. * @param {AbbreviationNode} tree
  9673. * @param {Object} profile
  9674. */
  9675. function process(tree, profile) {
  9676. if(tree.name) bemParse(tree, profile);
  9677. var abbrUtils = require('abbreviationUtils');
  9678. _.each(tree.children, function(item) {
  9679. process(item, profile);
  9680. if(!abbrUtils.isSnippet(item) && item.start) shouldRunHtmlFilter = true;
  9681. });
  9682. return tree;
  9683. };
  9684. require('filters').add('bem', function(tree, profile) {
  9685. shouldRunHtmlFilter = false;
  9686. tree = process(tree, profile);
  9687. // in case 'bem' filter is applied after 'html' filter: run it again
  9688. // to update output
  9689. if(shouldRunHtmlFilter) {
  9690. tree = require('filters').apply(tree, 'html', profile);
  9691. }
  9692. return tree;
  9693. });
  9694. });
  9695. /**
  9696. * Comment important tags (with 'id' and 'class' attributes)
  9697. * @author Sergey Chikuyonok (serge.che@gmail.com)
  9698. * @link http://chikuyonok.ru
  9699. * @constructor
  9700. * @memberOf __commentFilterDefine
  9701. * @param {Function} require
  9702. * @param {Underscore} _
  9703. */
  9704. emmet.exec(function(require, _) {
  9705. // define some preferences
  9706. /** @type emmet.preferences */
  9707. var prefs = require('preferences');
  9708. prefs.define('filter.commentAfter', '\n<!-- /<%= attr("id", "#") %><%= attr("class", ".") %> -->', 'A definition of comment that should be placed <i>after</i> matched ' + 'element when <code>comment</code> filter is applied. This definition ' + 'is an ERB-style template passed to <code>_.template()</code> ' + 'function (see Underscore.js docs for details). In template context, ' + 'the following properties and functions are availabe:\n' + '<ul>'
  9709. + '<li><code>attr(name, before, after)</code> – a function that outputs' + 'specified attribute value concatenated with <code>before</code> ' + 'and <code>after</code> strings. If attribute doesn\'t exists, the ' + 'empty string will be returned.</li>'
  9710. + '<li><code>node</code> – current node (instance of <code>AbbreviationNode</code>)</li>'
  9711. + '<li><code>name</code> – name of current tag</li>'
  9712. + '<li><code>padding</code> – current string padding, can be used ' + 'for formatting</li>'
  9713. + '</ul>');
  9714. prefs.define('filter.commentBefore', '', 'A definition of comment that should be placed <i>before</i> matched ' + 'element when <code>comment</code> filter is applied. ' + 'For more info, read description of <code>filter.commentAfter</code> ' + 'property');
  9715. prefs.define('filter.commentTrigger', 'id, class', 'A comma-separated list of attribute names that should exist in abbreviatoin ' + 'where comment should be added. If you wish to add comment for ' + 'every element, set this option to <code>*</code>');
  9716. /**
  9717. * Add comments to tag
  9718. * @param {AbbreviationNode} node
  9719. */
  9720. function addComments(node, templateBefore, templateAfter) {
  9721. var utils = require('utils');
  9722. // check if comments should be added
  9723. var trigger = prefs.get('filter.commentTrigger');
  9724. if(trigger != '*') {
  9725. var shouldAdd = _.find(trigger.split(','), function(name) {
  9726. return !!node.attribute(utils.trim(name));
  9727. });
  9728. if(!shouldAdd) return;
  9729. }
  9730. var ctx = {
  9731. node: node,
  9732. name: node.name(),
  9733. padding: node.parent ? node.parent.padding : '',
  9734. attr: function(name, before, after) {
  9735. var attr = node.attribute(name);
  9736. if(attr) {
  9737. return(before || '') + attr + (after || '');
  9738. }
  9739. return '';
  9740. }
  9741. };
  9742. var nodeBefore = utils.normalizeNewline(templateBefore ? templateBefore(ctx) : '');
  9743. var nodeAfter = utils.normalizeNewline(templateAfter ? templateAfter(ctx) : '');
  9744. node.start = node.start.replace(/</, nodeBefore + '<');
  9745. node.end = node.end.replace(/>/, '>' + nodeAfter);
  9746. }
  9747. function process(tree, before, after) {
  9748. var abbrUtils = require('abbreviationUtils');
  9749. _.each(tree.children, function(item) {
  9750. if(abbrUtils.isBlock(item)) addComments(item, before, after);
  9751. process(item, before, after);
  9752. });
  9753. return tree;
  9754. }
  9755. require('filters').add('c', function(tree) {
  9756. var templateBefore = _.template(prefs.get('filter.commentBefore'));
  9757. var templateAfter = _.template(prefs.get('filter.commentAfter'));
  9758. return process(tree, templateBefore, templateAfter);
  9759. });
  9760. });
  9761. /**
  9762. * Filter for escaping unsafe XML characters: <, >, &
  9763. * @author Sergey Chikuyonok (serge.che@gmail.com)
  9764. * @link http://chikuyonok.ru
  9765. */
  9766. emmet.exec(function(require, _) {
  9767. var charMap = {
  9768. '<': '&lt;',
  9769. '>': '&gt;',
  9770. '&': '&amp;'
  9771. };
  9772. function escapeChars(str) {
  9773. return str.replace(/([<>&])/g, function(str, p1) {
  9774. return charMap[p1];
  9775. });
  9776. }
  9777. require('filters').add('e', function process(tree) {
  9778. _.each(tree.children, function(item) {
  9779. item.start = escapeChars(item.start);
  9780. item.end = escapeChars(item.end);
  9781. item.content = escapeChars(item.content);
  9782. process(item);
  9783. });
  9784. return tree;
  9785. });
  9786. });
  9787. /**
  9788. * Generic formatting filter: creates proper indentation for each tree node,
  9789. * placing "%s" placeholder where the actual output should be. You can use
  9790. * this filter to preformat tree and then replace %s placeholder to whatever you
  9791. * need. This filter should't be called directly from editor as a part
  9792. * of abbreviation.
  9793. * @author Sergey Chikuyonok (serge.che@gmail.com)
  9794. * @link http://chikuyonok.ru
  9795. * @constructor
  9796. * @memberOf __formatFilterDefine
  9797. * @param {Function} require
  9798. * @param {Underscore} _
  9799. */
  9800. emmet.exec(function(require, _) {
  9801. var placeholder = '%s';
  9802. function getIndentation() {
  9803. return require('resources').getVariable('indentation');
  9804. }
  9805. /**
  9806. * Test if passed node has block-level sibling element
  9807. * @param {AbbreviationNode} item
  9808. * @return {Boolean}
  9809. */
  9810. function hasBlockSibling(item) {
  9811. return item.parent && require('abbreviationUtils').hasBlockChildren(item.parent);
  9812. }
  9813. /**
  9814. * Test if passed item is very first child in parsed tree
  9815. * @param {AbbreviationNode} item
  9816. */
  9817. function isVeryFirstChild(item) {
  9818. return item.parent && !item.parent.parent && !item.index();
  9819. }
  9820. /**
  9821. * Check if a newline should be added before element
  9822. * @param {AbbreviationNode} node
  9823. * @param {OutputProfile} profile
  9824. * @return {Boolean}
  9825. */
  9826. function shouldAddLineBreak(node, profile) {
  9827. var abbrUtils = require('abbreviationUtils');
  9828. if(profile.tag_nl === true || abbrUtils.isBlock(node)) return true;
  9829. if(!node.parent || !profile.inline_break) return false;
  9830. // check if there are required amount of adjacent inline element
  9831. var nodeCount = 0;
  9832. return !!_.find(node.parent.children, function(child) {
  9833. if(child.isTextNode() || !abbrUtils.isInline(child)) nodeCount = 0;
  9834. else if(abbrUtils.isInline(child)) nodeCount++;
  9835. if(nodeCount >= profile.inline_break) return true;
  9836. });
  9837. }
  9838. /**
  9839. * Need to add newline because <code>item</code> has too many inline children
  9840. * @param {AbbreviationNode} node
  9841. * @param {OutputProfile} profile
  9842. */
  9843. function shouldBreakChild(node, profile) {
  9844. // we need to test only one child element, because
  9845. // hasBlockChildren() method will do the rest
  9846. return node.children.length && shouldAddLineBreak(node.children[0], profile);
  9847. }
  9848. /**
  9849. * Processes element with matched resource of type <code>snippet</code>
  9850. * @param {AbbreviationNode} item
  9851. * @param {OutputProfile} profile
  9852. * @param {Number} level Depth level
  9853. */
  9854. function processSnippet(item, profile, level) {
  9855. if(!isVeryFirstChild(item)) {
  9856. item.start = require('utils').getNewline() + item.start;
  9857. }
  9858. return item;
  9859. }
  9860. /**
  9861. * Processes element with <code>tag</code> type
  9862. * @param {AbbreviationNode} item
  9863. * @param {OutputProfile} profile
  9864. * @param {Number} level Depth level
  9865. */
  9866. function processTag(item, profile, level) {
  9867. item.start = item.end = placeholder;
  9868. var utils = require('utils');
  9869. var abbrUtils = require('abbreviationUtils');
  9870. var isUnary = abbrUtils.isUnary(item);
  9871. var nl = utils.getNewline();
  9872. // formatting output
  9873. if(profile.tag_nl !== false) {
  9874. var forceNl = profile.tag_nl === true && (profile.tag_nl_leaf || item.children.length);
  9875. // formatting block-level elements
  9876. if(!item.isTextNode()) {
  9877. if(shouldAddLineBreak(item, profile)) {
  9878. // - do not indent the very first element
  9879. // - do not indent first child of a snippet
  9880. if(!isVeryFirstChild(item) && (!abbrUtils.isSnippet(item.parent) || item.index())) item.start = nl + item.start;
  9881. if(abbrUtils.hasBlockChildren(item) || shouldBreakChild(item, profile) || (forceNl && !isUnary)) item.end = nl + item.end;
  9882. if(abbrUtils.hasTagsInContent(item) || (forceNl && !item.children.length && !isUnary)) item.start += nl + getIndentation();
  9883. } else if(abbrUtils.isInline(item) && hasBlockSibling(item) && !isVeryFirstChild(item)) {
  9884. item.start = nl + item.start;
  9885. } else if(abbrUtils.isInline(item) && abbrUtils.hasBlockChildren(item)) {
  9886. item.end = nl + item.end;
  9887. }
  9888. item.padding = getIndentation();
  9889. }
  9890. }
  9891. return item;
  9892. }
  9893. /**
  9894. * Processes simplified tree, making it suitable for output as HTML structure
  9895. * @param {AbbreviationNode} tree
  9896. * @param {OutputProfile} profile
  9897. * @param {Number} level Depth level
  9898. */
  9899. require('filters').add('_format', function process(tree, profile, level) {
  9900. level = level || 0;
  9901. var abbrUtils = require('abbreviationUtils');
  9902. _.each(tree.children, function(item) {
  9903. if(abbrUtils.isSnippet(item)) processSnippet(item, profile, level);
  9904. else processTag(item, profile, level);
  9905. process(item, profile, level + 1);
  9906. });
  9907. return tree;
  9908. });
  9909. });
  9910. /**
  9911. * Filter for producing HAML code from abbreviation.
  9912. * @author Sergey Chikuyonok (serge.che@gmail.com)
  9913. * @link http://chikuyonok.ru
  9914. * @constructor
  9915. * @memberOf __hamlFilterDefine
  9916. * @param {Function} require
  9917. * @param {Underscore} _
  9918. */
  9919. emmet.exec(function(require, _) {
  9920. var childToken = '${child}';
  9921. function transformClassName(className) {
  9922. return require('utils').trim(className).replace(/\s+/g, '.');
  9923. }
  9924. /**
  9925. * Creates HAML attributes string from tag according to profile settings
  9926. * @param {AbbreviationNode} tag
  9927. * @param {Object} profile
  9928. */
  9929. function makeAttributesString(tag, profile) {
  9930. var attrs = '';
  9931. var otherAttrs = [];
  9932. var attrQuote = profile.attributeQuote();
  9933. var cursor = profile.cursor();
  9934. _.each(tag.attributeList(), function(a) {
  9935. var attrName = profile.attributeName(a.name);
  9936. switch(attrName.toLowerCase()) {
  9937. // use short notation for ID and CLASS attributes
  9938. case 'id':
  9939. attrs += '#' + (a.value || cursor);
  9940. break;
  9941. case 'class':
  9942. attrs += '.' + transformClassName(a.value || cursor);
  9943. break;
  9944. // process other attributes
  9945. default:
  9946. otherAttrs.push(':' + attrName + ' => ' + attrQuote + (a.value || cursor) + attrQuote);
  9947. }
  9948. });
  9949. if(otherAttrs.length) attrs += '{' + otherAttrs.join(', ') + '}';
  9950. return attrs;
  9951. }
  9952. /**
  9953. * Test if passed node has block-level sibling element
  9954. * @param {AbbreviationNode} item
  9955. * @return {Boolean}
  9956. */
  9957. function hasBlockSibling(item) {
  9958. return item.parent && item.parent.hasBlockChildren();
  9959. }
  9960. /**
  9961. * Processes element with <code>tag</code> type
  9962. * @param {AbbreviationNode} item
  9963. * @param {OutputProfile} profile
  9964. * @param {Number} level Depth level
  9965. */
  9966. function processTag(item, profile, level) {
  9967. if(!item.parent)
  9968. // looks like it's root element
  9969. return item;
  9970. var abbrUtils = require('abbreviationUtils');
  9971. var utils = require('utils');
  9972. var attrs = makeAttributesString(item, profile);
  9973. var cursor = profile.cursor();
  9974. var isUnary = abbrUtils.isUnary(item);
  9975. var selfClosing = profile.self_closing_tag && isUnary ? '/' : '';
  9976. var start = '';
  9977. // define tag name
  9978. var tagName = '%' + profile.tagName(item.name());
  9979. if(tagName.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1)
  9980. // omit div tag
  9981. tagName = '';
  9982. item.end = '';
  9983. start = tagName + attrs + selfClosing + ' ';
  9984. var placeholder = '%s';
  9985. // We can't just replace placeholder with new value because
  9986. // JavaScript will treat double $ character as a single one, assuming
  9987. // we're using RegExp literal.
  9988. item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder);
  9989. if(!item.children.length && !isUnary) item.start += cursor;
  9990. return item;
  9991. }
  9992. /**
  9993. * Processes simplified tree, making it suitable for output as HTML structure
  9994. * @param {AbbreviationNode} tree
  9995. * @param {Object} profile
  9996. * @param {Number} level Depth level
  9997. */
  9998. require('filters').add('haml', function process(tree, profile, level) {
  9999. level = level || 0;
  10000. var abbrUtils = require('abbreviationUtils');
  10001. if(!level) {
  10002. tree = require('filters').apply(tree, '_format', profile);
  10003. }
  10004. _.each(tree.children, function(item) {
  10005. if(!abbrUtils.isSnippet(item)) processTag(item, profile, level);
  10006. process(item, profile, level + 1);
  10007. });
  10008. return tree;
  10009. });
  10010. });
  10011. /**
  10012. * Filter that produces HTML tree
  10013. * @author Sergey Chikuyonok (serge.che@gmail.com)
  10014. * @link http://chikuyonok.ru
  10015. * @constructor
  10016. * @memberOf __htmlFilterDefine
  10017. * @param {Function} require
  10018. * @param {Underscore} _
  10019. */
  10020. emmet.exec(function(require, _) {
  10021. /**
  10022. * Creates HTML attributes string from tag according to profile settings
  10023. * @param {AbbreviationNode} node
  10024. * @param {OutputProfile} profile
  10025. */
  10026. function makeAttributesString(node, profile) {
  10027. var attrQuote = profile.attributeQuote();
  10028. var cursor = profile.cursor();
  10029. return _.map(node.attributeList(), function(a) {
  10030. var attrName = profile.attributeName(a.name);
  10031. return ' ' + attrName + '=' + attrQuote + (a.value || cursor) + attrQuote;
  10032. }).join('');
  10033. }
  10034. /**
  10035. * Processes element with <code>tag</code> type
  10036. * @param {AbbreviationNode} item
  10037. * @param {OutputProfile} profile
  10038. * @param {Number} level Depth level
  10039. */
  10040. function processTag(item, profile, level) {
  10041. if(!item.parent) // looks like it's root element
  10042. return item;
  10043. var abbrUtils = require('abbreviationUtils');
  10044. var utils = require('utils');
  10045. var attrs = makeAttributesString(item, profile);
  10046. var cursor = profile.cursor();
  10047. var isUnary = abbrUtils.isUnary(item);
  10048. var start = '';
  10049. var end = '';
  10050. // define opening and closing tags
  10051. if(!item.isTextNode()) {
  10052. var tagName = profile.tagName(item.name());
  10053. if(isUnary) {
  10054. start = '<' + tagName + attrs + profile.selfClosing() + '>';
  10055. item.end = '';
  10056. } else {
  10057. start = '<' + tagName + attrs + '>';
  10058. end = '</' + tagName + '>';
  10059. }
  10060. }
  10061. var placeholder = '%s';
  10062. // We can't just replace placeholder with new value because
  10063. // JavaScript will treat double $ character as a single one, assuming
  10064. // we're using RegExp literal.
  10065. item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder);
  10066. item.end = utils.replaceSubstring(item.end, end, item.end.indexOf(placeholder), placeholder);
  10067. if(!item.children.length && !isUnary && item.content.indexOf(cursor) == -1) item.start += cursor;
  10068. return item;
  10069. }
  10070. /**
  10071. * Processes simplified tree, making it suitable for output as HTML structure
  10072. * @param {AbbreviationNode} tree
  10073. * @param {Object} profile
  10074. * @param {Number} level Depth level
  10075. */
  10076. require('filters').add('html', function process(tree, profile, level) {
  10077. level = level || 0;
  10078. var abbrUtils = require('abbreviationUtils');
  10079. if(!level) {
  10080. tree = require('filters').apply(tree, '_format', profile);
  10081. }
  10082. _.each(tree.children, function(item) {
  10083. if(!abbrUtils.isSnippet(item)) processTag(item, profile, level);
  10084. process(item, profile, level + 1);
  10085. });
  10086. return tree;
  10087. });
  10088. });
  10089. /**
  10090. * Output abbreviation on a single line (i.e. no line breaks)
  10091. * @author Sergey Chikuyonok (serge.che@gmail.com)
  10092. * @link http://chikuyonok.ru
  10093. * @constructor
  10094. * @memberOf __singleLineFilterDefine
  10095. * @param {Function} require
  10096. * @param {Underscore} _
  10097. */
  10098. emmet.exec(function(require, _) {
  10099. var rePad = /^\s+/;
  10100. var reNl = /[\n\r]/g;
  10101. require('filters').add('s', function process(tree, profile, level) {
  10102. var abbrUtils = require('abbreviationUtils');
  10103. _.each(tree.children, function(item) {
  10104. if(!abbrUtils.isSnippet(item)) {
  10105. // remove padding from item
  10106. item.start = item.start.replace(rePad, '');
  10107. item.end = item.end.replace(rePad, '');
  10108. }
  10109. // remove newlines
  10110. item.start = item.start.replace(reNl, '');
  10111. item.end = item.end.replace(reNl, '');
  10112. item.content = item.content.replace(reNl, '');
  10113. process(item);
  10114. });
  10115. return tree;
  10116. });
  10117. });
  10118. /**
  10119. * Trim filter: removes characters at the beginning of the text
  10120. * content that indicates lists: numbers, #, *, -, etc.
  10121. *
  10122. * Useful for wrapping lists with abbreviation.
  10123. *
  10124. * @author Sergey Chikuyonok (serge.che@gmail.com)
  10125. * @link http://chikuyonok.ru
  10126. *
  10127. * @constructor
  10128. * @memberOf __trimFilterDefine
  10129. * @param {Function} require
  10130. * @param {Underscore} _
  10131. */
  10132. emmet.exec(function(require, _) {
  10133. require('preferences').define('filter.trimRegexp', '[\\s|\\u00a0]*[\\d|#|\\-|\*|\\u2022]+\\.?\\s*', 'Regular expression used to remove list markers (numbers, dashes, ' + 'bullets, etc.) in <code>t</code> (trim) filter. The trim filter ' + 'is useful for wrapping with abbreviation lists, pased from other ' + 'documents (for example, Word documents).');
  10134. function process(tree, re) {
  10135. _.each(tree.children, function(item) {
  10136. if(item.content) item.content = item.content.replace(re, '');
  10137. process(item, re);
  10138. });
  10139. return tree;
  10140. }
  10141. require('filters').add('t', function(tree) {
  10142. var re = new RegExp(require('preferences').get('filter.trimRegexp'));
  10143. return process(tree, re);
  10144. });
  10145. });
  10146. /**
  10147. * Filter for trimming "select" attributes from some tags that contains
  10148. * child elements
  10149. * @author Sergey Chikuyonok (serge.che@gmail.com)
  10150. * @link http://chikuyonok.ru
  10151. *
  10152. * @constructor
  10153. * @memberOf __xslFilterDefine
  10154. * @param {Function} require
  10155. * @param {Underscore} _
  10156. */
  10157. emmet.exec(function(require, _) {
  10158. var tags = {
  10159. 'xsl:variable': 1,
  10160. 'xsl:with-param': 1
  10161. };
  10162. /**
  10163. * Removes "select" attribute from node
  10164. * @param {AbbreviationNode} node
  10165. */
  10166. function trimAttribute(node) {
  10167. node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, '');
  10168. }
  10169. require('filters').add('xsl', function process(tree) {
  10170. var abbrUtils = require('abbreviationUtils');
  10171. _.each(tree.children, function(item) {
  10172. if(!abbrUtils.isSnippet(item) && (item.name() || '').toLowerCase() in tags && item.children.length) trimAttribute(item);
  10173. process(item);
  10174. });
  10175. return tree;
  10176. });
  10177. });
  10178. /**
  10179. * "Lorem ipsum" text generator. Matches <code>lipsum(num)?</code> or
  10180. * <code>lorem(num)?</code> abbreviation.
  10181. * This code is based on Django's contribution:
  10182. * https://code.djangoproject.com/browser/django/trunk/django/contrib/webdesign/lorem_ipsum.py
  10183. * <br><br>
  10184. * Examples to test:<br>
  10185. * <code>lipsum</code> – generates 30 words text.<br>
  10186. * <code>lipsum*6</code> – generates 6 paragraphs (autowrapped with &lt;p&gt; element) of text.<br>
  10187. * <code>ol>lipsum10*5</code> — generates ordered list with 5 list items (autowrapped with &lt;li&gt; tag)
  10188. * with text of 10 words on each line<br>
  10189. * <code>span*3>lipsum20</code> – generates 3 paragraphs of 20-words text, each wrapped with &lt;span&gt; element .
  10190. * Each paragraph phrase is unique
  10191. * @param {Function} require
  10192. * @param {Underscore} _
  10193. * @constructor
  10194. * @memberOf __loremIpsumGeneratorDefine
  10195. */
  10196. emmet.exec(function(require, _) {
  10197. /**
  10198. * @param {AbbreviationNode} tree
  10199. * @param {Object} options
  10200. */
  10201. require('abbreviationParser').addPreprocessor(function(tree, options) {
  10202. var re = /^(?:lorem|lipsum)(\d*)$/i,
  10203. match;
  10204. /** @param {AbbreviationNode} node */
  10205. tree.findAll(function(node) {
  10206. if(node._name && (match = node._name.match(re))) {
  10207. var wordCound = match[1] || 30;
  10208. // force node name resolving if node should be repeated
  10209. // or contains attributes. In this case, node should be outputed
  10210. // as tag, otherwise as text-only node
  10211. node._name = '';
  10212. node.data('forceNameResolving', node.isRepeating() || node.attributeList().length);
  10213. node.data('pasteOverwrites', true);
  10214. node.data('paste', function(i, content) {
  10215. return paragraph(wordCound, !i);
  10216. });
  10217. }
  10218. });
  10219. });
  10220. var COMMON_P = 'lorem ipsum dolor sit amet consectetur adipisicing elit'.split(' ');
  10221. var WORDS = ['exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet', 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi', 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi', 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos', 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum', 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus', 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus', 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum', 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem', 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus', 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente', 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet', 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta', 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima', 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim', 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores', 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias', 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea', 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt', 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate', 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius', 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos', 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore', 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo', 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi', 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam', 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique', 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere', 'maxime', 'corrupti'];
  10222. /**
  10223. * Returns random integer between <code>from</code> and <code>to</code> values
  10224. * @param {Number} from
  10225. * @param {Number} to
  10226. * @returns {Number}
  10227. */
  10228. function randint(from, to) {
  10229. return Math.round(Math.random() * (to - from) + from);
  10230. }
  10231. /**
  10232. * @param {Array} arr
  10233. * @param {Number} count
  10234. * @returns {Array}
  10235. */
  10236. function sample(arr, count) {
  10237. var len = arr.length;
  10238. var iterations = Math.min(len, count);
  10239. var result = [];
  10240. while(result.length < iterations) {
  10241. var randIx = randint(0, len - 1);
  10242. if(!_.include(result, randIx)) result.push(randIx);
  10243. }
  10244. return _.map(result, function(ix) {
  10245. return arr[ix];
  10246. });
  10247. }
  10248. function choice(val) {
  10249. if(_.isString(val)) return val.charAt(randint(0, val.length - 1));
  10250. return val[randint(0, val.length - 1)];
  10251. }
  10252. function sentence(words, end) {
  10253. if(words.length) {
  10254. words[0] = words[0].charAt(0).toUpperCase() + words[0].substring(1);
  10255. }
  10256. return words.join(' ') + (end || choice('?!...')); // more dots that question marks
  10257. }
  10258. /**
  10259. * Insert commas at randomly selected words. This function modifies values
  10260. * inside <code>words</code> array
  10261. * @param {Array} words
  10262. */
  10263. function insertCommas(words) {
  10264. var len = words.length;
  10265. var totalCommas = 0;
  10266. if(len > 3 && len <= 6) {
  10267. totalCommas = randint(0, 1);
  10268. } else if(len > 6 && len <= 12) {
  10269. totalCommas = randint(0, 2);
  10270. } else {
  10271. totalCommas = randint(1, 4);
  10272. }
  10273. _.each(sample(_.range(totalCommas)), function(ix) {
  10274. words[ix] += ',';
  10275. });
  10276. }
  10277. /**
  10278. * Generate a paragraph of "Lorem ipsum" text
  10279. * @param {Number} wordCount Words count in paragraph
  10280. * @param {Boolean} startWithCommon Should paragraph start with common
  10281. * "lorem ipsum" sentence.
  10282. * @returns {String}
  10283. */
  10284. function paragraph(wordCount, startWithCommon) {
  10285. var result = [];
  10286. var totalWords = 0;
  10287. var words;
  10288. wordCount = parseInt(wordCount, 10);
  10289. if(startWithCommon) {
  10290. words = COMMON_P.slice(0, wordCount);
  10291. if(words.length > 5) words[4] += ',';
  10292. totalWords += words.length;
  10293. result.push(sentence(words, '.'));
  10294. }
  10295. while(totalWords < wordCount) {
  10296. words = sample(WORDS, Math.min(randint(3, 12) * randint(1, 5), wordCount - totalWords));
  10297. totalWords += words.length;
  10298. insertCommas(words);
  10299. result.push(sentence(words));
  10300. }
  10301. return result.join(' ');
  10302. }
  10303. });
  10304. /**
  10305. * Select current line (for simple editors like browser's &lt;textarea&gt;)
  10306. */
  10307. emmet.exec(function(require, _) {
  10308. require('actions').add('select_line', function(editor) {
  10309. var range = editor.getCurrentLineRange();
  10310. editor.createSelection(range.start, range.end);
  10311. return true;
  10312. });
  10313. });
  10314. emmet.exec(function(require, _) {
  10315. require('resources').setVocabulary({
  10316. "variables": {
  10317. "lang": "en",
  10318. "locale": "en-US",
  10319. "charset": "UTF-8",
  10320. "indentation": "\t",
  10321. "newline": "\n"
  10322. },
  10323. "css": {
  10324. "filters": "html",
  10325. "snippets": {
  10326. "@i": "@import url(|);",
  10327. "@m": "@media print {\n\t|\n}",
  10328. "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}",
  10329. "@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}",
  10330. "!": "!important",
  10331. "pos": "position:|;",
  10332. "pos:s": "position:static;",
  10333. "pos:a": "position:absolute;",
  10334. "pos:r": "position:relative;",
  10335. "pos:f": "position:fixed;",
  10336. "t": "top:|;",
  10337. "t:a": "top:auto;",
  10338. "r": "right:|;",
  10339. "r:a": "right:auto;",
  10340. "b": "bottom:|;",
  10341. "b:a": "bottom:auto;",
  10342. "l": "left:|;",
  10343. "l:a": "left:auto;",
  10344. "z": "z-index:|;",
  10345. "z:a": "z-index:auto;",
  10346. "fl": "float:|;",
  10347. "fl:n": "float:none;",
  10348. "fl:l": "float:left;",
  10349. "fl:r": "float:right;",
  10350. "cl": "clear:|;",
  10351. "cl:n": "clear:none;",
  10352. "cl:l": "clear:left;",
  10353. "cl:r": "clear:right;",
  10354. "cl:b": "clear:both;",
  10355. "d": "display:|;",
  10356. "d:n": "display:none;",
  10357. "d:b": "display:block;",
  10358. "d:i": "display:inline;",
  10359. "d:ib": "display:inline-block;",
  10360. "d:li": "display:list-item;",
  10361. "d:ri": "display:run-in;",
  10362. "d:cp": "display:compact;",
  10363. "d:tb": "display:table;",
  10364. "d:itb": "display:inline-table;",
  10365. "d:tbcp": "display:table-caption;",
  10366. "d:tbcl": "display:table-column;",
  10367. "d:tbclg": "display:table-column-group;",
  10368. "d:tbhg": "display:table-header-group;",
  10369. "d:tbfg": "display:table-footer-group;",
  10370. "d:tbr": "display:table-row;",
  10371. "d:tbrg": "display:table-row-group;",
  10372. "d:tbc": "display:table-cell;",
  10373. "d:rb": "display:ruby;",
  10374. "d:rbb": "display:ruby-base;",
  10375. "d:rbbg": "display:ruby-base-group;",
  10376. "d:rbt": "display:ruby-text;",
  10377. "d:rbtg": "display:ruby-text-group;",
  10378. "v": "visibility:|;",
  10379. "v:v": "visibility:visible;",
  10380. "v:h": "visibility:hidden;",
  10381. "v:c": "visibility:collapse;",
  10382. "ov": "overflow:|;",
  10383. "ov:v": "overflow:visible;",
  10384. "ov:h": "overflow:hidden;",
  10385. "ov:s": "overflow:scroll;",
  10386. "ov:a": "overflow:auto;",
  10387. "ovx": "overflow-x:|;",
  10388. "ovx:v": "overflow-x:visible;",
  10389. "ovx:h": "overflow-x:hidden;",
  10390. "ovx:s": "overflow-x:scroll;",
  10391. "ovx:a": "overflow-x:auto;",
  10392. "ovy": "overflow-y:|;",
  10393. "ovy:v": "overflow-y:visible;",
  10394. "ovy:h": "overflow-y:hidden;",
  10395. "ovy:s": "overflow-y:scroll;",
  10396. "ovy:a": "overflow-y:auto;",
  10397. "ovs": "overflow-style:|;",
  10398. "ovs:a": "overflow-style:auto;",
  10399. "ovs:s": "overflow-style:scrollbar;",
  10400. "ovs:p": "overflow-style:panner;",
  10401. "ovs:m": "overflow-style:move;",
  10402. "ovs:mq": "overflow-style:marquee;",
  10403. "zoo": "zoom:1;",
  10404. "cp": "clip:|;",
  10405. "cp:a": "clip:auto;",
  10406. "cp:r": "clip:rect(|);",
  10407. "bxz": "box-sizing:|;",
  10408. "bxz:cb": "box-sizing:content-box;",
  10409. "bxz:bb": "box-sizing:border-box;",
  10410. "bxsh": "box-shadow:${1:hoff} ${2:voff} ${3:radius} ${4:color};",
  10411. "bxsh:n": "box-shadow:none;",
  10412. "m": "margin:|;",
  10413. "mt": "margin-top:|;",
  10414. "mt:a": "margin-top:auto;",
  10415. "mr": "margin-right:|;",
  10416. "mr:a": "margin-right:auto;",
  10417. "mb": "margin-bottom:|;",
  10418. "mb:a": "margin-bottom:auto;",
  10419. "ml": "margin-left:|;",
  10420. "ml:a": "margin-left:auto;",
  10421. "p": "padding:|;",
  10422. "pt": "padding-top:|;",
  10423. "pr": "padding-right:|;",
  10424. "pb": "padding-bottom:|;",
  10425. "pl": "padding-left:|;",
  10426. "w": "width:|;",
  10427. "w:a": "width:auto;",
  10428. "h": "height:|;",
  10429. "h:a": "height:auto;",
  10430. "maw": "max-width:|;",
  10431. "maw:n": "max-width:none;",
  10432. "mah": "max-height:|;",
  10433. "mah:n": "max-height:none;",
  10434. "miw": "min-width:|;",
  10435. "mih": "min-height:|;",
  10436. "o": "outline:|;",
  10437. "o:n": "outline:none;",
  10438. "oo": "outline-offset:|;",
  10439. "ow": "outline-width:|;",
  10440. "os": "outline-style:|;",
  10441. "oc": "outline-color:#${1:000};",
  10442. "oc:i": "outline-color:invert;",
  10443. "bd": "border:|;",
  10444. "bd+": "border:${1:1px} ${2:solid} ${3:#000};",
  10445. "bd:n": "border:none;",
  10446. "bdbk": "border-break:|;",
  10447. "bdbk:c": "border-break:close;",
  10448. "bdcl": "border-collapse:|;",
  10449. "bdcl:c": "border-collapse:collapse;",
  10450. "bdcl:s": "border-collapse:separate;",
  10451. "bdc": "border-color:#${1:000};",
  10452. "bdi": "border-image:url(|);",
  10453. "bdi:n": "border-image:none;",
  10454. "bdti": "border-top-image:url(|);",
  10455. "bdti:n": "border-top-image:none;",
  10456. "bdri": "border-right-image:url(|);",
  10457. "bdri:n": "border-right-image:none;",
  10458. "bdbi": "border-bottom-image:url(|);",
  10459. "bdbi:n": "border-bottom-image:none;",
  10460. "bdli": "border-left-image:url(|);",
  10461. "bdli:n": "border-left-image:none;",
  10462. "bdci": "border-corner-image:url(|);",
  10463. "bdci:n": "border-corner-image:none;",
  10464. "bdci:c": "border-corner-image:continue;",
  10465. "bdtli": "border-top-left-image:url(|);",
  10466. "bdtli:n": "border-top-left-image:none;",
  10467. "bdtli:c": "border-top-left-image:continue;",
  10468. "bdtri": "border-top-right-image:url(|);",
  10469. "bdtri:n": "border-top-right-image:none;",
  10470. "bdtri:c": "border-top-right-image:continue;",
  10471. "bdbri": "border-bottom-right-image:url(|);",
  10472. "bdbri:n": "border-bottom-right-image:none;",
  10473. "bdbri:c": "border-bottom-right-image:continue;",
  10474. "bdbli": "border-bottom-left-image:url(|);",
  10475. "bdbli:n": "border-bottom-left-image:none;",
  10476. "bdbli:c": "border-bottom-left-image:continue;",
  10477. "bdf": "border-fit:|;",
  10478. "bdf:c": "border-fit:clip;",
  10479. "bdf:r": "border-fit:repeat;",
  10480. "bdf:sc": "border-fit:scale;",
  10481. "bdf:st": "border-fit:stretch;",
  10482. "bdf:ow": "border-fit:overwrite;",
  10483. "bdf:of": "border-fit:overflow;",
  10484. "bdf:sp": "border-fit:space;",
  10485. "bdl": "border-length:|;",
  10486. "bdl:a": "border-length:auto;",
  10487. "bdsp": "border-spacing:|;",
  10488. "bds": "border-style:|;",
  10489. "bds:n": "border-style:none;",
  10490. "bds:h": "border-style:hidden;",
  10491. "bds:dt": "border-style:dotted;",
  10492. "bds:ds": "border-style:dashed;",
  10493. "bds:s": "border-style:solid;",
  10494. "bds:db": "border-style:double;",
  10495. "bds:dtds": "border-style:dot-dash;",
  10496. "bds:dtdtds": "border-style:dot-dot-dash;",
  10497. "bds:w": "border-style:wave;",
  10498. "bds:g": "border-style:groove;",
  10499. "bds:r": "border-style:ridge;",
  10500. "bds:i": "border-style:inset;",
  10501. "bds:o": "border-style:outset;",
  10502. "bdw": "border-width:|;",
  10503. "bdt": "border-top:|;",
  10504. "bdt+": "border-top:${1:1px} ${2:solid} ${3:#000};",
  10505. "bdt:n": "border-top:none;",
  10506. "bdtw": "border-top-width:|;",
  10507. "bdts": "border-top-style:|;",
  10508. "bdts:n": "border-top-style:none;",
  10509. "bdtc": "border-top-color:#${1:000};",
  10510. "bdr": "border-right:|;",
  10511. "bdr+": "border-right:${1:1px} ${2:solid} ${3:#000};",
  10512. "bdr:n": "border-right:none;",
  10513. "bdrw": "border-right-width:|;",
  10514. "bdrs": "border-right-style:|;",
  10515. "bdrs:n": "border-right-style:none;",
  10516. "bdrc": "border-right-color:#${1:000};",
  10517. "bdb": "border-bottom:|;",
  10518. "bdb+": "border-bottom:${1:1px} ${2:solid} ${3:#000};",
  10519. "bdb:n": "border-bottom:none;",
  10520. "bdbw": "border-bottom-width:|;",
  10521. "bdbs": "border-bottom-style:|;",
  10522. "bdbs:n": "border-bottom-style:none;",
  10523. "bdbc": "border-bottom-color:#${1:000};",
  10524. "bdl": "border-left:|;",
  10525. "bdl+": "border-left:${1:1px} ${2:solid} ${3:#000};",
  10526. "bdl:n": "border-left:none;",
  10527. "bdlw": "border-left-width:|;",
  10528. "bdls": "border-left-style:|;",
  10529. "bdls:n": "border-left-style:none;",
  10530. "bdlc": "border-left-color:#${1:000};",
  10531. "bdrs": "border-radius:|;",
  10532. "bdtrrs": "border-top-right-radius:|;",
  10533. "bdtlrs": "border-top-left-radius:|;",
  10534. "bdbrrs": "border-bottom-right-radius:|;",
  10535. "bdblrs": "border-bottom-left-radius:|;",
  10536. "bg": "background:|;",
  10537. "bg+": "background:${1:#fff} url(${2}) ${3:0} ${4:0} ${5:no-repeat};",
  10538. "bg:n": "background:none;",
  10539. "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');",
  10540. "bgc": "background-color:#${1:fff};",
  10541. "bgi": "background-image:url(|);",
  10542. "bgi:n": "background-image:none;",
  10543. "bgr": "background-repeat:|;",
  10544. "bgr:n": "background-repeat:no-repeat;",
  10545. "bgr:x": "background-repeat:repeat-x;",
  10546. "bgr:y": "background-repeat:repeat-y;",
  10547. "bga": "background-attachment:|;",
  10548. "bga:f": "background-attachment:fixed;",
  10549. "bga:s": "background-attachment:scroll;",
  10550. "bgp": "background-position:${1:0} ${2:0};",
  10551. "bgpx": "background-position-x:|;",
  10552. "bgpy": "background-position-y:|;",
  10553. "bgbk": "background-break:|;",
  10554. "bgbk:bb": "background-break:bounding-box;",
  10555. "bgbk:eb": "background-break:each-box;",
  10556. "bgbk:c": "background-break:continuous;",
  10557. "bgcp": "background-clip:|;",
  10558. "bgcp:bb": "background-clip:border-box;",
  10559. "bgcp:pb": "background-clip:padding-box;",
  10560. "bgcp:cb": "background-clip:content-box;",
  10561. "bgcp:nc": "background-clip:no-clip;",
  10562. "bgo": "background-origin:|;",
  10563. "bgo:pb": "background-origin:padding-box;",
  10564. "bgo:bb": "background-origin:border-box;",
  10565. "bgo:cb": "background-origin:content-box;",
  10566. "bgz": "background-size:|;",
  10567. "bgz:a": "background-size:auto;",
  10568. "bgz:ct": "background-size:contain;",
  10569. "bgz:cv": "background-size:cover;",
  10570. "c": "color:#${1:000};",
  10571. "cm": "/* |${child} */",
  10572. "cn": "content:|;",
  10573. "tbl": "table-layout:|;",
  10574. "tbl:a": "table-layout:auto;",
  10575. "tbl:f": "table-layout:fixed;",
  10576. "cps": "caption-side:|;",
  10577. "cps:t": "caption-side:top;",
  10578. "cps:b": "caption-side:bottom;",
  10579. "ec": "empty-cells:|;",
  10580. "ec:s": "empty-cells:show;",
  10581. "ec:h": "empty-cells:hide;",
  10582. "lis": "list-style:|;",
  10583. "lis:n": "list-style:none;",
  10584. "lisp": "list-style-position:|;",
  10585. "lisp:i": "list-style-position:inside;",
  10586. "lisp:o": "list-style-position:outside;",
  10587. "list": "list-style-type:|;",
  10588. "list:n": "list-style-type:none;",
  10589. "list:d": "list-style-type:disc;",
  10590. "list:c": "list-style-type:circle;",
  10591. "list:s": "list-style-type:square;",
  10592. "list:dc": "list-style-type:decimal;",
  10593. "list:dclz": "list-style-type:decimal-leading-zero;",
  10594. "list:lr": "list-style-type:lower-roman;",
  10595. "list:ur": "list-style-type:upper-roman;",
  10596. "lisi": "list-style-image:|;",
  10597. "lisi:n": "list-style-image:none;",
  10598. "q": "quotes:|;",
  10599. "q:n": "quotes:none;",
  10600. "q:ru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C';",
  10601. "q:en": "quotes:'\\201C' '\\201D' '\\2018' '\\2019';",
  10602. "ct": "content:|;",
  10603. "ct:n": "content:normal;",
  10604. "ct:oq": "content:open-quote;",
  10605. "ct:noq": "content:no-open-quote;",
  10606. "ct:cq": "content:close-quote;",
  10607. "ct:ncq": "content:no-close-quote;",
  10608. "ct:a": "content:attr(|);",
  10609. "ct:c": "content:counter(|);",
  10610. "ct:cs": "content:counters(|);",
  10611. "coi": "counter-increment:|;",
  10612. "cor": "counter-reset:|;",
  10613. "va": "vertical-align:|;",
  10614. "va:sup": "vertical-align:super;",
  10615. "va:t": "vertical-align:top;",
  10616. "va:tt": "vertical-align:text-top;",
  10617. "va:m": "vertical-align:middle;",
  10618. "va:bl": "vertical-align:baseline;",
  10619. "va:b": "vertical-align:bottom;",
  10620. "va:tb": "vertical-align:text-bottom;",
  10621. "va:sub": "vertical-align:sub;",
  10622. "ta": "text-align:|;",
  10623. "ta:l": "text-align:left;",
  10624. "ta:c": "text-align:center;",
  10625. "ta:r": "text-align:right;",
  10626. "tal": "text-align-last:|;",
  10627. "tal:a": "text-align-last:auto;",
  10628. "tal:l": "text-align-last:left;",
  10629. "tal:c": "text-align-last:center;",
  10630. "tal:r": "text-align-last:right;",
  10631. "td": "text-decoration:|;",
  10632. "td:n": "text-decoration:none;",
  10633. "td:u": "text-decoration:underline;",
  10634. "td:o": "text-decoration:overline;",
  10635. "td:l": "text-decoration:line-through;",
  10636. "te": "text-emphasis:|;",
  10637. "te:n": "text-emphasis:none;",
  10638. "te:ac": "text-emphasis:accent;",
  10639. "te:dt": "text-emphasis:dot;",
  10640. "te:c": "text-emphasis:circle;",
  10641. "te:ds": "text-emphasis:disc;",
  10642. "te:b": "text-emphasis:before;",
  10643. "te:a": "text-emphasis:after;",
  10644. "th": "text-height:|;",
  10645. "th:a": "text-height:auto;",
  10646. "th:f": "text-height:font-size;",
  10647. "th:t": "text-height:text-size;",
  10648. "th:m": "text-height:max-size;",
  10649. "ti": "text-indent:|;",
  10650. "ti:-": "text-indent:-9999px;",
  10651. "tj": "text-justify:|;",
  10652. "tj:a": "text-justify:auto;",
  10653. "tj:iw": "text-justify:inter-word;",
  10654. "tj:ii": "text-justify:inter-ideograph;",
  10655. "tj:ic": "text-justify:inter-cluster;",
  10656. "tj:d": "text-justify:distribute;",
  10657. "tj:k": "text-justify:kashida;",
  10658. "tj:t": "text-justify:tibetan;",
  10659. "to": "text-outline:|;",
  10660. "to+": "text-outline:${1:0} ${2:0} ${3:#000};",
  10661. "to:n": "text-outline:none;",
  10662. "tr": "text-replace:|;",
  10663. "tr:n": "text-replace:none;",
  10664. "tt": "text-transform:|;",
  10665. "tt:n": "text-transform:none;",
  10666. "tt:c": "text-transform:capitalize;",
  10667. "tt:u": "text-transform:uppercase;",
  10668. "tt:l": "text-transform:lowercase;",
  10669. "tw": "text-wrap:|;",
  10670. "tw:n": "text-wrap:normal;",
  10671. "tw:no": "text-wrap:none;",
  10672. "tw:u": "text-wrap:unrestricted;",
  10673. "tw:s": "text-wrap:suppress;",
  10674. "tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000};",
  10675. "tsh+": "text-shadow:${1:0} ${2:0} ${3:0} ${4:#000};",
  10676. "tsh:n": "text-shadow:none;",
  10677. "trf": "transform:|;",
  10678. "trf:skx": "transform: skewX(${1:angle});",
  10679. "trf:sky": "transform: skewY(${1:angle});",
  10680. "trf:sc": "transform: scale(${1:x}, ${2:y});",
  10681. "trf:scx": "transform: scaleX(${1:x});",
  10682. "trf:scy": "transform: scaleY(${1:y});",
  10683. "trf:r": "transform: rotate(${1:angle});",
  10684. "trf:t": "transform: translate(${1:x}, ${2:y});",
  10685. "trf:tx": "transform: translateX(${1:x});",
  10686. "trf:ty": "transform: translateY(${1:y});",
  10687. "trs": "transition:${1:prop} ${2:time};",
  10688. "trsde": "transition-delay:${1:time};",
  10689. "trsdu": "transition-duration:${1:time};",
  10690. "trsp": "transition-property:${1:prop};",
  10691. "trstf": "transition-timing-function:${1:tfunc};",
  10692. "lh": "line-height:|;",
  10693. "whs": "white-space:|;",
  10694. "whs:n": "white-space:normal;",
  10695. "whs:p": "white-space:pre;",
  10696. "whs:nw": "white-space:nowrap;",
  10697. "whs:pw": "white-space:pre-wrap;",
  10698. "whs:pl": "white-space:pre-line;",
  10699. "whsc": "white-space-collapse:|;",
  10700. "whsc:n": "white-space-collapse:normal;",
  10701. "whsc:k": "white-space-collapse:keep-all;",
  10702. "whsc:l": "white-space-collapse:loose;",
  10703. "whsc:bs": "white-space-collapse:break-strict;",
  10704. "whsc:ba": "white-space-collapse:break-all;",
  10705. "wob": "word-break:|;",
  10706. "wob:n": "word-break:normal;",
  10707. "wob:k": "word-break:keep-all;",
  10708. "wob:l": "word-break:loose;",
  10709. "wob:bs": "word-break:break-strict;",
  10710. "wob:ba": "word-break:break-all;",
  10711. "wos": "word-spacing:|;",
  10712. "wow": "word-wrap:|;",
  10713. "wow:nm": "word-wrap:normal;",
  10714. "wow:n": "word-wrap:none;",
  10715. "wow:u": "word-wrap:unrestricted;",
  10716. "wow:s": "word-wrap:suppress;",
  10717. "lts": "letter-spacing:|;",
  10718. "f": "font:|;",
  10719. "f+": "font:${1:1em} ${2:Arial,sans-serif};",
  10720. "fw": "font-weight:|;",
  10721. "fw:n": "font-weight:normal;",
  10722. "fw:b": "font-weight:bold;",
  10723. "fw:br": "font-weight:bolder;",
  10724. "fw:lr": "font-weight:lighter;",
  10725. "fs": "font-style:|;",
  10726. "fs:n": "font-style:normal;",
  10727. "fs:i": "font-style:italic;",
  10728. "fs:o": "font-style:oblique;",
  10729. "fv": "font-variant:|;",
  10730. "fv:n": "font-variant:normal;",
  10731. "fv:sc": "font-variant:small-caps;",
  10732. "fz": "font-size:|;",
  10733. "fza": "font-size-adjust:|;",
  10734. "fza:n": "font-size-adjust:none;",
  10735. "ff": "font-family:|;",
  10736. "ff:s": "font-family:serif;",
  10737. "ff:ss": "font-family:sans-serif;",
  10738. "ff:c": "font-family:cursive;",
  10739. "ff:f": "font-family:fantasy;",
  10740. "ff:m": "font-family:monospace;",
  10741. "fef": "font-effect:|;",
  10742. "fef:n": "font-effect:none;",
  10743. "fef:eg": "font-effect:engrave;",
  10744. "fef:eb": "font-effect:emboss;",
  10745. "fef:o": "font-effect:outline;",
  10746. "fem": "font-emphasize:|;",
  10747. "femp": "font-emphasize-position:|;",
  10748. "femp:b": "font-emphasize-position:before;",
  10749. "femp:a": "font-emphasize-position:after;",
  10750. "fems": "font-emphasize-style:|;",
  10751. "fems:n": "font-emphasize-style:none;",
  10752. "fems:ac": "font-emphasize-style:accent;",
  10753. "fems:dt": "font-emphasize-style:dot;",
  10754. "fems:c": "font-emphasize-style:circle;",
  10755. "fems:ds": "font-emphasize-style:disc;",
  10756. "fsm": "font-smooth:|;",
  10757. "fsm:a": "font-smooth:auto;",
  10758. "fsm:n": "font-smooth:never;",
  10759. "fsm:aw": "font-smooth:always;",
  10760. "fst": "font-stretch:|;",
  10761. "fst:n": "font-stretch:normal;",
  10762. "fst:uc": "font-stretch:ultra-condensed;",
  10763. "fst:ec": "font-stretch:extra-condensed;",
  10764. "fst:c": "font-stretch:condensed;",
  10765. "fst:sc": "font-stretch:semi-condensed;",
  10766. "fst:se": "font-stretch:semi-expanded;",
  10767. "fst:e": "font-stretch:expanded;",
  10768. "fst:ee": "font-stretch:extra-expanded;",
  10769. "fst:ue": "font-stretch:ultra-expanded;",
  10770. "op": "opacity:|;",
  10771. "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);",
  10772. "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';",
  10773. "rz": "resize:|;",
  10774. "rz:n": "resize:none;",
  10775. "rz:b": "resize:both;",
  10776. "rz:h": "resize:horizontal;",
  10777. "rz:v": "resize:vertical;",
  10778. "cur": "cursor:|;",
  10779. "cur:a": "cursor:auto;",
  10780. "cur:d": "cursor:default;",
  10781. "cur:c": "cursor:crosshair;",
  10782. "cur:ha": "cursor:hand;",
  10783. "cur:he": "cursor:help;",
  10784. "cur:m": "cursor:move;",
  10785. "cur:p": "cursor:pointer;",
  10786. "cur:t": "cursor:text;",
  10787. "pgbb": "page-break-before:|;",
  10788. "pgbb:au": "page-break-before:auto;",
  10789. "pgbb:al": "page-break-before:always;",
  10790. "pgbb:l": "page-break-before:left;",
  10791. "pgbb:r": "page-break-before:right;",
  10792. "pgbi": "page-break-inside:|;",
  10793. "pgbi:au": "page-break-inside:auto;",
  10794. "pgbi:av": "page-break-inside:avoid;",
  10795. "pgba": "page-break-after:|;",
  10796. "pgba:au": "page-break-after:auto;",
  10797. "pgba:al": "page-break-after:always;",
  10798. "pgba:l": "page-break-after:left;",
  10799. "pgba:r": "page-break-after:right;",
  10800. "orp": "orphans:|;",
  10801. "wid": "widows:|;"
  10802. }
  10803. },
  10804. "html": {
  10805. "filters": "html",
  10806. "snippets": {
  10807. "c": "<!-- |${child} -->",
  10808. "cc:ie6": "<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->",
  10809. "cc:ie": "<!--[if IE]>\n\t${child}|\n<![endif]-->",
  10810. "cc:noie": "<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->",
  10811. "html:4t": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html lang=\"${lang}\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\">\n\t<title>${1:Document}</title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>",
  10812. "html:4s": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html lang=\"${lang}\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\">\n\t<title>${1:Document}</title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>",
  10813. "html:xt": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"${lang}\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\" />\n\t<title></title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>",
  10814. "html:xs": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"${lang}\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\" />\n\t<title>${1:Document}</title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>",
  10815. "html:xxs": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"${lang}\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\" />\n\t<title>${1:Document}</title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>",
  10816. "html:5": "<!doctype html>\n<html lang=\"${lang}\">\n<head>\n\t<meta charset=\"${charset}\">\n\t<title>${1:Document}</title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>"
  10817. },
  10818. "abbreviations": {
  10819. "!": "html:5",
  10820. "a": "<a href=\"\">",
  10821. "a:link": "<a href=\"http://|\">",
  10822. "a:mail": "<a href=\"mailto:|\">",
  10823. "abbr": "<abbr title=\"\">",
  10824. "acronym": "<acronym title=\"\">",
  10825. "base": "<base href=\"\" />",
  10826. "bdo": "<bdo dir=\"\">",
  10827. "bdo:r": "<bdo dir=\"rtl\">",
  10828. "bdo:l": "<bdo dir=\"ltr\">",
  10829. "link": "<link rel=\"stylesheet\" href=\"\" />",
  10830. "link:css": "<link rel=\"stylesheet\" href=\"${1:style}.css\" media=\"all\" />",
  10831. "link:print": "<link rel=\"stylesheet\" href=\"${1:print}.css\" media=\"print\" />",
  10832. "link:favicon": "<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"${1:favicon.ico}\" />",
  10833. "link:touch": "<link rel=\"apple-touch-icon\" href=\"${1:favicon.png}\" />",
  10834. "link:rss": "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"${1:rss.xml}\" />",
  10835. "link:atom": "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"Atom\" href=\"${1:atom.xml}\" />",
  10836. "meta:utf": "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\" />",
  10837. "meta:win": "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=windows-1251\" />",
  10838. "meta:compat": "<meta http-equiv=\"X-UA-Compatible\" content=\"${1:IE=7}\" />",
  10839. "style": "<style>",
  10840. "script": "<script>",
  10841. "script:src": "<script src=\"\">",
  10842. "img": "<img src=\"\" alt=\"\" />",
  10843. "iframe": "<iframe src=\"\" frameborder=\"0\">",
  10844. "embed": "<embed src=\"\" type=\"\" />",
  10845. "object": "<object data=\"\" type=\"\">",
  10846. "param": "<param name=\"\" value=\"\" />",
  10847. "map": "<map name=\"\">",
  10848. "area": "<area shape=\"\" coords=\"\" href=\"\" alt=\"\" />",
  10849. "area:d": "<area shape=\"default\" href=\"\" alt=\"\" />",
  10850. "area:c": "<area shape=\"circle\" coords=\"\" href=\"\" alt=\"\" />",
  10851. "area:r": "<area shape=\"rect\" coords=\"\" href=\"\" alt=\"\" />",
  10852. "area:p": "<area shape=\"poly\" coords=\"\" href=\"\" alt=\"\" />",
  10853. "form": "<form action=\"\">",
  10854. "form:get": "<form action=\"\" method=\"get\">",
  10855. "form:post": "<form action=\"\" method=\"post\">",
  10856. "label": "<label for=\"\">",
  10857. "input": "<input type=\"\" />",
  10858. "input:hidden": "<input type=\"hidden\" name=\"\" />",
  10859. "input:h": "<input type=\"hidden\" name=\"\" />",
  10860. "input:text": "<input type=\"text\" name=\"\" id=\"\" />",
  10861. "input:t": "<input type=\"text\" name=\"\" id=\"\" />",
  10862. "input:search": "<input type=\"search\" name=\"\" id=\"\" />",
  10863. "input:email": "<input type=\"email\" name=\"\" id=\"\" />",
  10864. "input:url": "<input type=\"url\" name=\"\" id=\"\" />",
  10865. "input:password": "<input type=\"password\" name=\"\" id=\"\" />",
  10866. "input:p": "<input type=\"password\" name=\"\" id=\"\" />",
  10867. "input:datetime": "<input type=\"datetime\" name=\"\" id=\"\" />",
  10868. "input:date": "<input type=\"date\" name=\"\" id=\"\" />",
  10869. "input:datetime-local": "<input type=\"datetime-local\" name=\"\" id=\"\" />",
  10870. "input:month": "<input type=\"month\" name=\"\" id=\"\" />",
  10871. "input:week": "<input type=\"week\" name=\"\" id=\"\" />",
  10872. "input:time": "<input type=\"time\" name=\"\" id=\"\" />",
  10873. "input:number": "<input type=\"number\" name=\"\" id=\"\" />",
  10874. "input:color": "<input type=\"color\" name=\"\" id=\"\" />",
  10875. "input:checkbox": "<input type=\"checkbox\" name=\"\" id=\"\" />",
  10876. "input:c": "<input type=\"checkbox\" name=\"\" id=\"\" />",
  10877. "input:radio": "<input type=\"radio\" name=\"\" id=\"\" />",
  10878. "input:r": "<input type=\"radio\" name=\"\" id=\"\" />",
  10879. "input:range": "<input type=\"range\" name=\"\" id=\"\" />",
  10880. "input:file": "<input type=\"file\" name=\"\" id=\"\" />",
  10881. "input:f": "<input type=\"file\" name=\"\" id=\"\" />",
  10882. "input:submit": "<input type=\"submit\" value=\"\" />",
  10883. "input:s": "<input type=\"submit\" value=\"\" />",
  10884. "input:image": "<input type=\"image\" src=\"\" alt=\"\" />",
  10885. "input:i": "<input type=\"image\" src=\"\" alt=\"\" />",
  10886. "input:reset": "<input type=\"reset\" value=\"\" />",
  10887. "input:button": "<input type=\"button\" value=\"\" />",
  10888. "input:b": "<input type=\"button\" value=\"\" />",
  10889. "select": "<select name=\"\" id=\"\"></select>",
  10890. "option": "<option value=\"\"></option>",
  10891. "textarea": "<textarea name=\"\" id=\"\" cols=\"${1:30}\" rows=\"${2:10}\">",
  10892. "menu:context": "<menu type=\"context\">",
  10893. "menu:c": "<menu type=\"context\">",
  10894. "menu:toolbar": "<menu type=\"toolbar\">",
  10895. "menu:t": "<menu type=\"toolbar\">",
  10896. "video": "<video src=\"\">",
  10897. "audio": "<audio src=\"\">",
  10898. "html:xml": "<html xmlns=\"http://www.w3.org/1999/xhtml\">",
  10899. "bq": "blockquote",
  10900. "acr": "acronym",
  10901. "fig": "figure",
  10902. "figc": "figcaption",
  10903. "ifr": "iframe",
  10904. "emb": "embed",
  10905. "obj": "object",
  10906. "src": "source",
  10907. "cap": "caption",
  10908. "colg": "colgroup",
  10909. "fst": "fieldset",
  10910. "btn": "button",
  10911. "optg": "optgroup",
  10912. "opt": "option",
  10913. "tarea": "textarea",
  10914. "leg": "legend",
  10915. "sect": "section",
  10916. "art": "article",
  10917. "hdr": "header",
  10918. "ftr": "footer",
  10919. "adr": "address",
  10920. "dlg": "dialog",
  10921. "str": "strong",
  10922. "prog": "progress",
  10923. "fset": "fieldset",
  10924. "datag": "datagrid",
  10925. "datal": "datalist",
  10926. "kg": "keygen",
  10927. "out": "output",
  10928. "det": "details",
  10929. "cmd": "command",
  10930. "ol+": "ol>li",
  10931. "ul+": "ul>li",
  10932. "dl+": "dl>dt+dd",
  10933. "map+": "map>area",
  10934. "table+": "table>tr>td",
  10935. "colgroup+": "colgroup>col",
  10936. "colg+": "colgroup>col",
  10937. "tr+": "tr>td",
  10938. "select+": "select>option",
  10939. "optgroup+": "optgroup>option",
  10940. "optg+": "optgroup>option"
  10941. }
  10942. },
  10943. "xml": {
  10944. "extends": "html",
  10945. "profile": "xml",
  10946. "filters": "html"
  10947. },
  10948. "xsl": {
  10949. "extends": "html",
  10950. "filters": "html, xsl",
  10951. "abbreviations": {
  10952. "tm": "<xsl:template match=\"\" mode=\"\">",
  10953. "tmatch": "tm",
  10954. "tn": "<xsl:template name=\"\">",
  10955. "tname": "tn",
  10956. "call": "<xsl:call-template name=\"\"/>",
  10957. "ap": "<xsl:apply-templates select=\"\" mode=\"\"/>",
  10958. "api": "<xsl:apply-imports/>",
  10959. "imp": "<xsl:import href=\"\"/>",
  10960. "inc": "<xsl:include href=\"\"/>",
  10961. "ch": "<xsl:choose>",
  10962. "xsl:when": "<xsl:when test=\"\">",
  10963. "wh": "xsl:when",
  10964. "ot": "<xsl:otherwise>",
  10965. "if": "<xsl:if test=\"\">",
  10966. "par": "<xsl:param name=\"\">",
  10967. "pare": "<xsl:param name=\"\" select=\"\"/>",
  10968. "var": "<xsl:variable name=\"\">",
  10969. "vare": "<xsl:variable name=\"\" select=\"\"/>",
  10970. "wp": "<xsl:with-param name=\"\" select=\"\"/>",
  10971. "key": "<xsl:key name=\"\" match=\"\" use=\"\"/>",
  10972. "elem": "<xsl:element name=\"\">",
  10973. "attr": "<xsl:attribute name=\"\">",
  10974. "attrs": "<xsl:attribute-set name=\"\">",
  10975. "cp": "<xsl:copy select=\"\"/>",
  10976. "co": "<xsl:copy-of select=\"\"/>",
  10977. "val": "<xsl:value-of select=\"\"/>",
  10978. "each": "<xsl:for-each select=\"\">",
  10979. "for": "each",
  10980. "tex": "<xsl:text></xsl:text>",
  10981. "com": "<xsl:comment>",
  10982. "msg": "<xsl:message terminate=\"no\">",
  10983. "fall": "<xsl:fallback>",
  10984. "num": "<xsl:number value=\"\"/>",
  10985. "nam": "<namespace-alias stylesheet-prefix=\"\" result-prefix=\"\"/>",
  10986. "pres": "<xsl:preserve-space elements=\"\"/>",
  10987. "strip": "<xsl:strip-space elements=\"\"/>",
  10988. "proc": "<xsl:processing-instruction name=\"\">",
  10989. "sort": "<xsl:sort select=\"\" order=\"\"/>",
  10990. "choose+": "xsl:choose>xsl:when+xsl:otherwise"
  10991. }
  10992. },
  10993. "haml": {
  10994. "filters": "haml",
  10995. "extends": "html"
  10996. },
  10997. "scss": {
  10998. "extends": "css"
  10999. },
  11000. "sass": {
  11001. "extends": "css"
  11002. },
  11003. "less": {
  11004. "extends": "css"
  11005. },
  11006. "stylus": {
  11007. "extends": "css"
  11008. }
  11009. }, 'system');
  11010. });
  11011. /**
  11012. * Tests if passed keydown/keypress event corresponds to defied shortcut
  11013. *
  11014. * Based on http://www.openjs.com/scripts/events/keyboard_shortcuts/
  11015. * By Binny V A
  11016. * License : BSD
  11017. */
  11018. emmet.define('shortcut', function() {
  11019. var is_opera = !! window.opera,
  11020. is_mac = /mac\s+os/i.test(navigator.userAgent),
  11021. //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
  11022. shift_nums = {
  11023. "`": "~",
  11024. "1": "!",
  11025. "2": "@",
  11026. "3": "#",
  11027. "4": "$",
  11028. "5": "%",
  11029. "6": "^",
  11030. "7": "&",
  11031. "8": "*",
  11032. "9": "(",
  11033. "0": ")",
  11034. "-": "_",
  11035. "=": "+",
  11036. ";": ":",
  11037. "'": "\"",
  11038. ",": "<",
  11039. ".": ">",
  11040. "/": "?",
  11041. "\\": "|"
  11042. },
  11043. //Special Keys - and their codes
  11044. special_keys = {
  11045. 'esc': 27,
  11046. 'escape': 27,
  11047. 'tab': 9,
  11048. 'space': 32,
  11049. 'return': 13,
  11050. 'enter': 13,
  11051. 'backspace': 8,
  11052. 'scrolllock': 145,
  11053. 'scroll_lock': 145,
  11054. 'scroll': 145,
  11055. 'capslock': 20,
  11056. 'caps_lock': 20,
  11057. 'caps': 20,
  11058. 'numlock': 144,
  11059. 'num_lock': 144,
  11060. 'num': 144,
  11061. 'pause': 19,
  11062. 'break': 19,
  11063. 'insert': 45,
  11064. 'home': 36,
  11065. 'delete': 46,
  11066. 'end': 35,
  11067. 'pageup': 33,
  11068. 'page_up': 33,
  11069. 'pu': 33,
  11070. 'pagedown': 34,
  11071. 'page_down': 34,
  11072. 'pd': 34,
  11073. 'plus': 187,
  11074. 'minus': 189,
  11075. 'left': 37,
  11076. 'up': 38,
  11077. 'right': 39,
  11078. 'down': 40,
  11079. 'f1': 112,
  11080. 'f2': 113,
  11081. 'f3': 114,
  11082. 'f4': 115,
  11083. 'f5': 116,
  11084. 'f6': 117,
  11085. 'f7': 118,
  11086. 'f8': 119,
  11087. 'f9': 120,
  11088. 'f10': 121,
  11089. 'f11': 122,
  11090. 'f12': 123
  11091. },
  11092. mac_char_map = {
  11093. 'ctrl': '⌃',
  11094. 'control': '⌃',
  11095. 'meta': '⌘',
  11096. 'shift': '⇧',
  11097. 'alt': '⌥',
  11098. 'enter': '⏎',
  11099. 'tab': '⇥',
  11100. 'left': '←',
  11101. 'right': '→',
  11102. 'up': '↑',
  11103. 'down': '↓'
  11104. },
  11105. pc_char_map = {
  11106. 'meta': 'Ctrl',
  11107. 'control': 'Ctrl',
  11108. 'left': '←',
  11109. 'right': '→',
  11110. 'up': '↑',
  11111. 'down': '↓'
  11112. },
  11113. MODIFIERS = {
  11114. SHIFT: 1,
  11115. CTRL: 2,
  11116. ALT: 4,
  11117. META: 8
  11118. };
  11119. /**
  11120. * Makes first letter of string in uppercase
  11121. * @param {String} str
  11122. */
  11123. function capitalize(str) {
  11124. return str.charAt().toUpperCase() + str.substring(1);
  11125. }
  11126. return {
  11127. /**
  11128. * Compile keyboard combination for faster tests
  11129. * @param {String|Object} combination
  11130. */
  11131. compile: function(combination) {
  11132. if(typeof combination != 'string') //already compiled
  11133. return combination;
  11134. var mask = 0,
  11135. keys = combination.toLowerCase().split('+'),
  11136. key, k;
  11137. for(var i = 0, il = keys.length; i < il; i++) {
  11138. k = keys[i];
  11139. // Due to stupid Opera bug I have to swap Ctrl and Meta keys
  11140. if(is_mac && is_opera) {
  11141. if(k == 'ctrl' || k == 'control') k = 'meta';
  11142. else if(k == 'meta') k = 'ctrl';
  11143. } else if(!is_mac && k == 'meta') {
  11144. k = 'ctrl';
  11145. }
  11146. //Modifiers
  11147. if(k == 'ctrl' || k == 'control') mask |= MODIFIERS.CTRL;
  11148. else if(k == 'shift') mask |= MODIFIERS.SHIFT;
  11149. else if(k == 'alt') mask |= MODIFIERS.ALT;
  11150. else if(k == 'meta') mask |= MODIFIERS.META;
  11151. else key = k;
  11152. }
  11153. return {
  11154. mask: mask,
  11155. key: key
  11156. };
  11157. },
  11158. /**
  11159. * Test shortcut combination against event
  11160. * @param {String} combination Keyboard shortcut
  11161. * @param {Event} evt
  11162. */
  11163. test: function(combination, evt) {
  11164. var mask = 0,
  11165. ccomb = this.compile(combination);
  11166. if(evt.ctrlKey) mask |= MODIFIERS.CTRL;
  11167. if(evt.shiftKey) mask |= MODIFIERS.SHIFT;
  11168. if(evt.altKey) mask |= MODIFIERS.ALT;
  11169. if(evt.metaKey) mask |= MODIFIERS.META;
  11170. var code = evt.keyCode ? evt.keyCode : evt.which,
  11171. character = String.fromCharCode(code).toLowerCase();
  11172. // if mask doesn't match, no need to test further
  11173. if(mask !== ccomb.mask) return false;
  11174. if(ccomb.key.length > 1) { //If it is a special key
  11175. return special_keys[ccomb.key] == code;
  11176. } else { //The special keys did not match
  11177. if(code == 188) character = ","; //If the user presses , when the type is onkeydown
  11178. if(code == 190) character = ".";
  11179. if(code == 191) character = "/";
  11180. if(character == ccomb.key) return true;
  11181. if(evt.shiftKey && shift_nums[character]) //Stupid Shift key bug created by using lowercase
  11182. return shift_nums[character] == ccomb.key;
  11183. }
  11184. return false;
  11185. },
  11186. /**
  11187. * Format keystroke for better readability, considering current platform
  11188. * mnemonics
  11189. * @param {String} keystroke
  11190. * @return {String}
  11191. */
  11192. format: function(keystroke) {
  11193. var char_map = is_mac ? mac_char_map : pc_char_map,
  11194. glue = is_mac ? '' : '+',
  11195. keys = keystroke.toLowerCase().split('+'),
  11196. ar = [],
  11197. key;
  11198. for(var i = 0; i < keys.length; i++) {
  11199. key = keys[i];
  11200. ar.push(key in char_map ? char_map[key] : capitalize(key));
  11201. }
  11202. return ar.join(glue);
  11203. }
  11204. };
  11205. });
  11206. /**
  11207. * @author Sergey Chikuyonok (serge.che@gmail.com)
  11208. * @link http://chikuyonok.ru
  11209. * @constructor
  11210. * @memberOf __emmetEditorTextarea
  11211. * @param {Function} require
  11212. * @param {Underscore} _
  11213. */
  11214. emmet.define('editor', function(require, _) { /** @param {Element} Source element */
  11215. var target = null;
  11216. // different browser uses different newlines, so we have to figure out
  11217. // native browser newline and sanitize incoming text with them
  11218. var tx = document.createElement('textarea');
  11219. tx.value = '\n';
  11220. require('utils').setNewline(tx.value);
  11221. tx = null;
  11222. return {
  11223. setContext: function(elem) {
  11224. target = elem;
  11225. },
  11226. getContext: function() {
  11227. return target;
  11228. },
  11229. getSelectionRange: function() {
  11230. if('selectionStart' in target) { // W3C's DOM
  11231. return {
  11232. start: target.selectionStart,
  11233. end: target.selectionEnd
  11234. };
  11235. } else if(document.selection) { // IE
  11236. target.focus();
  11237. var range = document.selection.createRange();
  11238. if(range === null) {
  11239. return {
  11240. start: 0,
  11241. end: this.getContent().length
  11242. };
  11243. }
  11244. var re = target.createTextRange();
  11245. var rc = re.duplicate();
  11246. re.moveToBookmark(range.getBookmark());
  11247. rc.setEndPoint('EndToStart', re);
  11248. return {
  11249. start: rc.text.length,
  11250. end: rc.text.length + range.text.length
  11251. };
  11252. } else {
  11253. return null;
  11254. }
  11255. },
  11256. /**
  11257. * Creates text selection on target element
  11258. * @param {Number} start
  11259. * @param {Number} end
  11260. */
  11261. createSelection: function(start, end) {
  11262. // W3C's DOM
  11263. if(typeof(end) == 'undefined') end = start;
  11264. if('setSelectionRange' in target) {
  11265. target.setSelectionRange(start, end);
  11266. } else if('createTextRange' in target) {
  11267. var t = target.createTextRange();
  11268. t.collapse(true);
  11269. var utils = require('utils');
  11270. var delta = utils.splitByLines(this.getContent().substring(0, start)).length - 1;
  11271. // IE has an issue with handling newlines while creating selection,
  11272. // so we need to adjust start and end indexes
  11273. end -= delta + utils.splitByLines(this.getContent().substring(start, end)).length - 1;
  11274. start -= delta;
  11275. t.moveStart('character', start);
  11276. t.moveEnd('character', end - start);
  11277. t.select();
  11278. }
  11279. },
  11280. /**
  11281. * Returns current line's start and end indexes
  11282. */
  11283. getCurrentLineRange: function() {
  11284. var caretPos = this.getCaretPos();
  11285. if(caretPos === null) return null;
  11286. return require('utils').findNewlineBounds(this.getContent(), caretPos);
  11287. },
  11288. /**
  11289. * Returns current caret position
  11290. * @return {Number}
  11291. */
  11292. getCaretPos: function() {
  11293. var selection = this.getSelectionRange();
  11294. return selection ? selection.start : null;
  11295. },
  11296. /**
  11297. * Set new caret position
  11298. * @param {Number} pos Caret position
  11299. */
  11300. setCaretPos: function(pos) {
  11301. this.createSelection(pos);
  11302. },
  11303. /**
  11304. * Returns content of current line
  11305. * @return {String}
  11306. */
  11307. getCurrentLine: function() {
  11308. var range = this.getCurrentLineRange();
  11309. return range.start < range.end ? this.getContent().substring(range.start, range.end) : '';
  11310. },
  11311. /**
  11312. * Replace editor's content or it's part (from <code>start</code> to
  11313. * <code>end</code> index). If <code>value</code> contains
  11314. * <code>caret_placeholder</code>, the editor will put caret into
  11315. * this position. If you skip <code>start</code> and <code>end</code>
  11316. * arguments, the whole target's content will be replaced with
  11317. * <code>value</code>.
  11318. *
  11319. * If you pass <code>start</code> argument only,
  11320. * the <code>value</code> will be placed at <code>start</code> string
  11321. * index of current content.
  11322. *
  11323. * If you pass <code>start</code> and <code>end</code> arguments,
  11324. * the corresponding substring of current target's content will be
  11325. * replaced with <code>value</code>.
  11326. * @param {String} value Content you want to paste
  11327. * @param {Number} start Start index of editor's content
  11328. * @param {Number} end End index of editor's content
  11329. * @param {Boolean} noIndent Do not auto indent <code>value</code>
  11330. */
  11331. replaceContent: function(value, start, end, noIndent) {
  11332. var content = this.getContent();
  11333. var utils = require('utils');
  11334. if(_.isUndefined(end)) end = _.isUndefined(start) ? content.length : start;
  11335. if(_.isUndefined(start)) start = 0;
  11336. // indent new value
  11337. if(!noIndent) {
  11338. value = utils.padString(value, utils.getLinePaddingFromPosition(content, start));
  11339. }
  11340. // find new caret position
  11341. var tabstopData = emmet.require('tabStops').extract(value, {
  11342. escape: function(ch) {
  11343. return ch;
  11344. }
  11345. });
  11346. value = tabstopData.text;
  11347. var firstTabStop = tabstopData.tabstops[0];
  11348. if(firstTabStop) {
  11349. firstTabStop.start += start;
  11350. firstTabStop.end += start;
  11351. } else {
  11352. firstTabStop = {
  11353. start: value.length + start,
  11354. end: value.length + start
  11355. };
  11356. }
  11357. try {
  11358. target.value = utils.replaceSubstring(content, value, start, end);
  11359. this.createSelection(firstTabStop.start, firstTabStop.end);
  11360. } catch(e) {}
  11361. },
  11362. /**
  11363. * Returns editor's content
  11364. * @return {String}
  11365. */
  11366. getContent: function() {
  11367. return target.value || '';
  11368. },
  11369. /**
  11370. * Returns current editor's syntax mode
  11371. * @return {String}
  11372. */
  11373. getSyntax: function() {
  11374. var syntax = require('textarea').getOption('syntax');
  11375. var caretPos = this.getCaretPos();
  11376. if(!require('resources').hasSyntax(syntax)) syntax = 'html';
  11377. if(syntax == 'html') {
  11378. // get the context tag
  11379. var pair = require('html_matcher').getTags(this.getContent(), caretPos);
  11380. if(pair && pair[0] && pair[0].type == 'tag' && pair[0].name.toLowerCase() == 'style') {
  11381. // check that we're actually inside the tag
  11382. if(pair[0].end <= caretPos && pair[1].start >= caretPos) syntax = 'css';
  11383. }
  11384. }
  11385. return syntax;
  11386. },
  11387. /**
  11388. * Returns current output profile name (@see emmet#setupProfile)
  11389. * @return {String}
  11390. */
  11391. getProfileName: function() {
  11392. return require('textarea').getOption('profile');
  11393. },
  11394. /**
  11395. * Ask user to enter something
  11396. * @param {String} title Dialog title
  11397. * @return {String} Entered data
  11398. * @since 0.65
  11399. */
  11400. prompt: function(title) {
  11401. return prompt(title);
  11402. },
  11403. /**
  11404. * Returns current selection
  11405. * @return {String}
  11406. * @since 0.65
  11407. */
  11408. getSelection: function() {
  11409. var sel = this.getSelectionRange();
  11410. if(sel) {
  11411. try {
  11412. return this.getContent().substring(sel.start, sel.end);
  11413. } catch(e) {}
  11414. }
  11415. return '';
  11416. },
  11417. /**
  11418. * Returns current editor's file path
  11419. * @return {String}
  11420. * @since 0.65
  11421. */
  11422. getFilePath: function() {
  11423. return location.href;
  11424. }
  11425. };
  11426. });
  11427. /**
  11428. * Controller for Emmet for textarea plugin: handles user interaction
  11429. * and calls Emmet commands
  11430. * @param {Function} require
  11431. * @param {Underscore} _
  11432. */
  11433. emmet.define('textarea', function(require, _) {
  11434. var keymap = {
  11435. 'Meta+E': 'expand_abbreviation',
  11436. 'Tab': 'expand_abbreviation',
  11437. 'Meta+D': 'match_pair_outward',
  11438. 'Shift+Meta+D': 'match_pair_inward',
  11439. 'Shift+Meta+A': 'wrap_with_abbreviation',
  11440. 'Ctrl+Alt+Right': 'next_edit_point',
  11441. 'Ctrl+Alt+Left': 'prev_edit_point',
  11442. 'Meta+L': 'select_line',
  11443. 'Meta+Shift+M': 'merge_lines',
  11444. 'Meta+/': 'toggle_comment',
  11445. 'Meta+J': 'split_join_tag',
  11446. 'Meta+K': 'remove_tag',
  11447. 'Shift+Meta+Y': 'evaluate_math_expression',
  11448. 'Ctrl+Up': 'increment_number_by_1',
  11449. 'Ctrl+Down': 'decrement_number_by_1',
  11450. 'Alt+Up': 'increment_number_by_01',
  11451. 'Alt+Down': 'decrement_number_by_01',
  11452. 'Ctrl+Alt+Up': 'increment_number_by_10',
  11453. 'Ctrl+Alt+Down': 'decrement_number_by_10',
  11454. 'Meta+.': 'select_next_item',
  11455. 'Meta+,': 'select_previous_item',
  11456. 'Meta+Shift+B': 'reflect_css_value',
  11457. 'Enter': 'insert_formatted_line_break'
  11458. };
  11459. var defaultOptions = {
  11460. profile: 'xhtml',
  11461. syntax: 'html',
  11462. use_tab: false,
  11463. pretty_break: false
  11464. };
  11465. var keyboardShortcuts = {};
  11466. var options = {};
  11467. /**
  11468. * Get Emmet options from element's class name
  11469. */
  11470. function getOptionsFromContext() {
  11471. var paramStr = require('editor').getContext().className || '';
  11472. var reParam = /\bemmet\-(\w+)\-(\w+)/g;
  11473. var result = copyOptions(options);
  11474. var m;
  11475. while((m = reParam.exec(paramStr))) {
  11476. var key = m[1].toLowerCase(),
  11477. value = m[2].toLowerCase();
  11478. if(value == 'true' || value == 'yes' || value == '1') value = true;
  11479. else if(value == 'false' || value == 'no' || value == '0') value = false;
  11480. result[key] = value;
  11481. }
  11482. return result;
  11483. }
  11484. function getOption(name) {
  11485. return getOptionsFromContext()[name];
  11486. }
  11487. function copyOptions(opt) {
  11488. return _.extend({}, defaultOptions, opt || {});
  11489. }
  11490. /**
  11491. * Bind shortcut to Emmet action
  11492. * @param {String} keystroke
  11493. * @param {String} label
  11494. * @param {String} actionName
  11495. */
  11496. function addShortcut(keystroke, actionName) {
  11497. keyboardShortcuts[keystroke.toLowerCase()] = {
  11498. compiled: require('shortcut').compile(keystroke),
  11499. action: actionName
  11500. };
  11501. }
  11502. function stopEvent(evt) {
  11503. evt.cancelBubble = true;
  11504. evt.returnValue = false;
  11505. if(evt.stopPropagation) {
  11506. evt.stopPropagation();
  11507. evt.preventDefault();
  11508. }
  11509. }
  11510. /**
  11511. * Runs actions called by user
  11512. * @param {Event} evt Event object
  11513. */
  11514. function runAction(evt) {
  11515. evt = evt || window.event;
  11516. /** @type {Element} */
  11517. var targetElem = evt.target || evt.srcElement;
  11518. var keyCode = evt.keyCode || evt.which;
  11519. var editor = require('editor');
  11520. var shortcut = require('shortcut');
  11521. if(targetElem && targetElem.nodeType == 1 && targetElem.nodeName == 'TEXTAREA') {
  11522. editor.setContext(targetElem);
  11523. // test if occurred event corresponds to one of the defined shortcut
  11524. return !_.find(keyboardShortcuts, function(sh) {
  11525. if(shortcut.test(sh.compiled, evt)) {
  11526. var actionName = sh.action;
  11527. switch(actionName) {
  11528. case 'expand_abbreviation':
  11529. if(keyCode == 9) {
  11530. if(getOption('use_tab')) actionName = 'expand_abbreviation_with_tab';
  11531. else
  11532. // user pressed Tab key but it's forbidden in
  11533. // Emmet: bubble up event
  11534. return false;
  11535. }
  11536. break;
  11537. case 'insert_formatted_line_break':
  11538. if(keyCode == 13 && !getOption('pretty_break')) {
  11539. // user pressed Enter but it's forbidden in
  11540. // Emmet: bubble up event
  11541. return false;
  11542. }
  11543. break;
  11544. }
  11545. require('actions').run(actionName, editor);
  11546. stopEvent(evt);
  11547. return true;
  11548. }
  11549. });
  11550. }
  11551. // allow event bubbling
  11552. return true;
  11553. }
  11554. var doc = document;
  11555. var keyEvent = window.opera ? 'keypress' : 'keydown';
  11556. //Attach the function with the event
  11557. if(doc.addEventListener) doc.addEventListener(keyEvent, runAction, false);
  11558. else if(doc.attachEvent) ele.attachEvent('on' + keyEvent, runAction);
  11559. else doc['on' + keyEvent] = func;
  11560. options = copyOptions();
  11561. _.each(keymap, function(actionName, keystroke) {
  11562. addShortcut(keystroke, actionName);
  11563. });
  11564. return {
  11565. /**
  11566. * Custom editor method: set default options (like syntax, tabs,
  11567. * etc.) for editor
  11568. * @param {Object} opt
  11569. */
  11570. setOptions: function(opt) {
  11571. options = copyOptions(opt);
  11572. },
  11573. /**
  11574. * Custom method: returns current context's option value
  11575. * @param {String} name Option name
  11576. * @return {String}
  11577. */
  11578. getOption: getOption,
  11579. addShortcut: addShortcut,
  11580. /**
  11581. * Removes shortcut binding
  11582. * @param {String} keystroke
  11583. */
  11584. unbindShortcut: function(keystroke) {
  11585. keystroke = keystroke.toLowerCase();
  11586. if(keystroke in keyboardShortcuts) delete keyboardShortcuts[keystroke];
  11587. },
  11588. /**
  11589. * Returns array of binded actions and their keystrokes
  11590. * @return {Array}
  11591. */
  11592. getShortcuts: function() {
  11593. var shortcut = require('shortcut');
  11594. var actions = require('actions');
  11595. return _.compact(_.map(keyboardShortcuts, function(sh, key) {
  11596. var keyLower = key.toLowerCase();
  11597. // skip some internal bindings
  11598. if(keyLower == 'tab' || keyLower == 'enter') return;
  11599. return {
  11600. keystroke: shortcut.format(key),
  11601. compiled: sh.compiled,
  11602. label: _.last((actions.get(sh.action).options.label || sh.action).split('/')),
  11603. action: sh.action
  11604. };
  11605. }));
  11606. },
  11607. getInfo: function() {
  11608. var message = 'This textareas on this page are powered by Emmet toolkit.\n\n' + 'Available shortcuts:\n';
  11609. var actions = _.map(this.getShortcuts(), function(shortcut) {
  11610. return shortcut.keystroke + ' — ' + shortcut.label;
  11611. });
  11612. message += actions.join('\n') + '\n\n';
  11613. message += 'More info on http://emmet.io/';
  11614. return message;
  11615. },
  11616. /**
  11617. * Show info window about Emmet
  11618. */
  11619. showInfo: function() {
  11620. alert(this.getInfo());
  11621. },
  11622. /**
  11623. * Setup editor. Pass object with values defined in
  11624. * <code>default_options</code>
  11625. */
  11626. setup: function(opt) {
  11627. this.setOptions(opt);
  11628. }
  11629. };
  11630. });