UNPKG

jasmine-fixture

Version:

Makes injecting HTML snippets into the DOM easy & clean!

1,594 lines (1,390 loc) 464 kB
// The MIT License (MIT) // // Copyright (c) 2012 Sergey Chikuyonok <serge.che@gmail.com> // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in the // Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN // AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.emmet=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"./lib/emmet.js":[function(require,module,exports){ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var global = typeof self != 'undefined' ? self : this; var utils = require('./utils/common'); var actions = require('./action/main'); var parser = require('./parser/abbreviation'); var file = require('./plugin/file'); var preferences = require('./assets/preferences'); var resources = require('./assets/resources'); var profile = require('./assets/profile'); var ciu = require('./assets/caniuse'); var logger = require('./assets/logger'); var sliceFn = Array.prototype.slice; /** * Returns file name part from path * @param {String} path Path to file * @return {String} */ function getFileName(path) { var re = /([\w\.\-]+)$/i; var m = re.exec(path); return m ? m[1] : ''; } /** * Normalizes profile definition: converts some * properties to valid data types * @param {Object} profile * @return {Object} */ function normalizeProfile(profile) { if (typeof profile === 'object') { if ('indent' in profile) { profile.indent = !!profile.indent; } if ('self_closing_tag' in profile) { if (typeof profile.self_closing_tag === 'number') { profile.self_closing_tag = !!profile.self_closing_tag; } } } return profile; } return { /** * The essential function that expands Emmet abbreviation * @param {String} abbr Abbreviation to parse * @param {String} syntax Abbreviation's context syntax * @param {String} profile Output profile (or its name) * @param {Object} contextNode Contextual node where abbreviation is * written * @return {String} */ expandAbbreviation: function(abbr, syntax, profile, contextNode) { return parser.expand(abbr, { syntax: syntax, profile: profile, contextNode: contextNode }); }, /** * Runs given action * @param {String} name Action name * @param {IEmmetEditor} editor Editor instance * @return {Boolean} Returns true if action was performed successfully */ run: function(name) { return actions.run.apply(actions, sliceFn.call(arguments, 0)); }, /** * Loads Emmet extensions. Extensions are simple .js files that * uses Emmet modules and resources to create new actions, modify * existing ones etc. * @param {Array} fileList List of absolute paths to files in extensions * folder. Back-end app should not filter this list (e.g. by extension) * but return it "as-is" so bootstrap can decide how to load contents * of each file. * This method requires a <code>file</code> module of <code>IEmmetFile</code> * interface to be implemented. * @memberOf bootstrap */ loadExtensions: function(fileList) { var payload = {}; var userSnippets = null; var that = this; // make sure file list contians only valid extension files fileList = fileList.filter(function(f) { var ext = file.getExt(f); return ext === 'json' || ext === 'js'; }); var reader = (file.readText || file.read).bind(file); var next = function() { if (fileList.length) { var f = fileList.shift(); reader(f, function(err, content) { if (err) { logger.log('Unable to read "' + f + '" file: '+ err); return next(); } switch (file.getExt(f)) { case 'js': try { eval(content); } catch (e) { logger.log('Unable to eval "' + f + '" file: '+ e); } break; case 'json': var fileName = getFileName(f).toLowerCase().replace(/\.json$/, ''); if (/^snippets/.test(fileName)) { if (fileName === 'snippets') { // data in snippets.json is more important to user userSnippets = utils.parseJSON(content); } else { payload.snippets = utils.deepMerge(payload.snippets || {}, utils.parseJSON(content)); } } else { payload[fileName] = content; } break; } next(); }); } else { // complete if (userSnippets) { payload.snippets = utils.deepMerge(payload.snippets || {}, userSnippets); } that.loadUserData(payload); } }; next(); }, /** * Loads preferences from JSON object (or string representation of JSON) * @param {Object} data * @returns */ loadPreferences: function(data) { preferences.load(utils.parseJSON(data)); }, /** * Loads user snippets and abbreviations. It doesn’t replace current * user resource vocabulary but merges it with passed one. If you need * to <i>replaces</i> user snippets you should call * <code>resetSnippets()</code> method first */ loadSnippets: function(data) { data = utils.parseJSON(data); var userData = resources.getVocabulary('user') || {}; resources.setVocabulary(utils.deepMerge(userData, data), 'user'); }, /** * Helper function that loads default snippets, defined in project’s * <i>snippets.json</i> * @param {Object} data */ loadSystemSnippets: function(data) { resources.setVocabulary(utils.parseJSON(data), 'system'); }, /** * Helper function that loads Can I Use database * @param {Object} data */ loadCIU: function(data) { ciu.load(utils.parseJSON(data)); }, /** * Removes all user-defined snippets */ resetSnippets: function() { resources.setVocabulary({}, 'user'); }, /** * Helper function that loads all user data (snippets and preferences) * defined as a single JSON object. This is useful for loading data * stored in a common storage, for example <code>NSUserDefaults</code> * @param {Object} data */ loadUserData: function(data) { data = utils.parseJSON(data); if (data.snippets) { this.loadSnippets(data.snippets); } if (data.preferences) { this.loadPreferences(data.preferences); } if (data.profiles) { this.loadProfiles(data.profiles); } if (data.caniuse) { this.loadCIU(data.caniuse); } var profiles = data.syntaxProfiles || data.syntaxprofiles; if (profiles) { this.loadSyntaxProfiles(profiles); } }, /** * Resets all user-defined data: preferences, snippets etc. * @returns */ resetUserData: function() { this.resetSnippets(); preferences.reset(); profile.reset(); }, /** * Load syntax-specific output profiles. These are essentially * an extension to syntax snippets * @param {Object} profiles Dictionary of profiles */ loadSyntaxProfiles: function(profiles) { profiles = utils.parseJSON(profiles); var snippets = {}; Object.keys(profiles).forEach(function(syntax) { var options = profiles[syntax]; if (!(syntax in snippets)) { snippets[syntax] = {}; } snippets[syntax].profile = normalizeProfile(options); }); this.loadSnippets(snippets); }, /** * Load named profiles * @param {Object} profiles */ loadProfiles: function(profiles) { profiles = utils.parseJSON(profiles); Object.keys(profiles).forEach(function(name) { profile.create(name, normalizeProfile(profiles[name])); }); }, require: require, // expose some useful data for plugin authors actions: actions, parser: parser, file: file, preferences: preferences, resources: resources, profile: profile, tabStops: require('./assets/tabStops'), htmlMatcher: require('./assets/htmlMatcher'), utils: { common: utils, action: require('./utils/action'), editor: require('./utils/editor') } }; }); },{"./action/main":"action/main.js","./assets/caniuse":"assets/caniuse.js","./assets/htmlMatcher":"assets/htmlMatcher.js","./assets/logger":"assets/logger.js","./assets/preferences":"assets/preferences.js","./assets/profile":"assets/profile.js","./assets/resources":"assets/resources.js","./assets/tabStops":"assets/tabStops.js","./parser/abbreviation":"parser/abbreviation.js","./plugin/file":"plugin/file.js","./utils/action":"utils/action.js","./utils/common":"utils/common.js","./utils/editor":"utils/editor.js"}],"action/balance.js":[function(require,module,exports){ /** * HTML pair matching (balancing) actions * @constructor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var htmlMatcher = require('../assets/htmlMatcher'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var range = require('../assets/range'); var cssEditTree = require('../editTree/css'); var cssSections = require('../utils/cssSections'); var lastMatch = null; function last(arr) { return arr[arr.length - 1]; } function balanceHTML(editor, direction) { var info = editorUtils.outputInfo(editor); var content = info.content; var sel = range(editor.getSelectionRange()); // validate previous match if (lastMatch && !lastMatch.range.equal(sel)) { lastMatch = null; } if (lastMatch && sel.length()) { if (direction == 'in') { // user has previously selected tag and wants to move inward if (lastMatch.type == 'tag' && !lastMatch.close) { // unary tag was selected, can't move inward return false; } else { if (lastMatch.range.equal(lastMatch.outerRange)) { lastMatch.range = lastMatch.innerRange; } else { var narrowed = utils.narrowToNonSpace(content, lastMatch.innerRange); lastMatch = htmlMatcher.find(content, narrowed.start + 1); if (lastMatch && lastMatch.range.equal(sel) && lastMatch.outerRange.equal(sel)) { lastMatch.range = lastMatch.innerRange; } } } } else { if ( !lastMatch.innerRange.equal(lastMatch.outerRange) && lastMatch.range.equal(lastMatch.innerRange) && sel.equal(lastMatch.range)) { lastMatch.range = lastMatch.outerRange; } else { lastMatch = htmlMatcher.find(content, sel.start); if (lastMatch && lastMatch.range.equal(sel) && lastMatch.innerRange.equal(sel)) { lastMatch.range = lastMatch.outerRange; } } } } else { lastMatch = htmlMatcher.find(content, sel.start); } if (lastMatch) { if (lastMatch.innerRange.equal(sel)) { lastMatch.range = lastMatch.outerRange; } if (!lastMatch.range.equal(sel)) { editor.createSelection(lastMatch.range.start, lastMatch.range.end); return true; } } lastMatch = null; return false; } function rangesForCSSRule(rule, pos) { // find all possible ranges var ranges = [rule.range(true)]; // braces content ranges.push(rule.valueRange(true)); // find nested sections var nestedSections = cssSections.nestedSectionsInRule(rule); // real content, e.g. from first property name to // last property value var items = rule.list(); if (items.length || nestedSections.length) { var start = Number.POSITIVE_INFINITY, end = -1; if (items.length) { start = items[0].namePosition(true); end = last(items).range(true).end; } if (nestedSections.length) { if (nestedSections[0].start < start) { start = nestedSections[0].start; } if (last(nestedSections).end > end) { end = last(nestedSections).end; } } ranges.push(range.create2(start, end)); } ranges = ranges.concat(nestedSections); var prop = cssEditTree.propertyFromPosition(rule, pos) || items[0]; if (prop) { ranges.push(prop.range(true)); var valueRange = prop.valueRange(true); if (!prop.end()) { valueRange._unterminated = true; } ranges.push(valueRange); } return ranges; } /** * Returns all possible selection ranges for given caret position * @param {String} content CSS content * @param {Number} pos Caret position(where to start searching) * @return {Array} */ function getCSSRanges(content, pos) { var rule; if (typeof content === 'string') { var ruleRange = cssSections.matchEnclosingRule(content, pos); if (ruleRange) { rule = cssEditTree.parse(ruleRange.substring(content), { offset: ruleRange.start }); } } else { // passed parsed CSS rule rule = content; } if (!rule) { return null; } // find all possible ranges var ranges = rangesForCSSRule(rule, pos); // remove empty ranges ranges = ranges.filter(function(item) { return !!item.length; }); return utils.unique(ranges, function(item) { return item.valueOf(); }); } function balanceCSS(editor, direction) { var info = editorUtils.outputInfo(editor); var content = info.content; var sel = range(editor.getSelectionRange()); var ranges = getCSSRanges(info.content, sel.start); if (!ranges && sel.length()) { // possible reason: user has already selected // CSS rule from last match try { var rule = cssEditTree.parse(sel.substring(info.content), { offset: sel.start }); ranges = getCSSRanges(rule, sel.start); } catch(e) {} } if (!ranges) { return false; } ranges = range.sort(ranges, true); // edge case: find match that equals current selection, // in case if user moves inward after selecting full CSS rule var bestMatch = utils.find(ranges, function(r) { return r.equal(sel); }); if (!bestMatch) { bestMatch = utils.find(ranges, function(r) { // Check for edge case: caret right after CSS value // but it doesn‘t contains terminating semicolon. // In this case we have to check full value range return r._unterminated ? r.include(sel.start) : r.inside(sel.start); }); } if (!bestMatch) { return false; } // if best match equals to current selection, move index // one position up or down, depending on direction var bestMatchIx = ranges.indexOf(bestMatch); if (bestMatch.equal(sel)) { bestMatchIx += direction == 'out' ? 1 : -1; } if (bestMatchIx < 0 || bestMatchIx >= ranges.length) { if (bestMatchIx >= ranges.length && direction == 'out') { pos = bestMatch.start - 1; var outerRanges = getCSSRanges(content, pos); if (outerRanges) { bestMatch = last(outerRanges.filter(function(r) { return r.inside(pos); })); } } else if (bestMatchIx < 0 && direction == 'in') { bestMatch = null; } else { bestMatch = null; } } else { bestMatch = ranges[bestMatchIx]; } if (bestMatch) { editor.createSelection(bestMatch.start, bestMatch.end); return true; } return false; } return { /** * Find and select HTML tag pair * @param {IEmmetEditor} editor Editor instance * @param {String} direction Direction of pair matching: 'in' or 'out'. * Default is 'out' */ balance: function(editor, direction) { direction = String((direction || 'out').toLowerCase()); var info = editorUtils.outputInfo(editor); if (actionUtils.isSupportedCSS(info.syntax)) { return balanceCSS(editor, direction); } return balanceHTML(editor, direction); }, balanceInwardAction: function(editor) { return this.balance(editor, 'in'); }, balanceOutwardAction: function(editor) { return this.balance(editor, 'out'); }, /** * Moves caret to matching opening or closing tag * @param {IEmmetEditor} editor */ goToMatchingPairAction: function(editor) { var content = String(editor.getContent()); var caretPos = editor.getCaretPos(); if (content.charAt(caretPos) == '<') // looks like caret is outside of tag pair caretPos++; var tag = htmlMatcher.tag(content, caretPos); if (tag && tag.close) { // exclude unary tags if (tag.open.range.inside(caretPos)) { editor.setCaretPos(tag.close.range.start); } else { editor.setCaretPos(tag.open.range.start); } return true; } return false; } }; }); },{"../assets/htmlMatcher":"assets/htmlMatcher.js","../assets/range":"assets/range.js","../editTree/css":"editTree/css.js","../utils/action":"utils/action.js","../utils/common":"utils/common.js","../utils/cssSections":"utils/cssSections.js","../utils/editor":"utils/editor.js"}],"action/base64.js":[function(require,module,exports){ /** * Encodes/decodes image under cursor to/from base64 * @param {IEmmetEditor} editor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var file = require('../plugin/file'); var base64 = require('../utils/base64'); var actionUtils = require('../utils/action'); var editorUtils = require('../utils/editor'); /** * Test if <code>text</code> starts with <code>token</code> at <code>pos</code> * position. If <code>pos</code> is omitted, search from beginning of text * @param {String} token Token to test * @param {String} text Where to search * @param {Number} pos Position where to start search * @return {Boolean} * @since 0.65 */ function startsWith(token, text, pos) { pos = pos || 0; return text.charAt(pos) == token.charAt(0) && text.substr(pos, token.length) == token; } /** * Encodes image to base64 * * @param {IEmmetEditor} editor * @param {String} imgPath Path to image * @param {Number} pos Caret position where image is located in the editor * @return {Boolean} */ function encodeToBase64(editor, imgPath, pos) { var editorFile = editor.getFilePath(); var defaultMimeType = 'application/octet-stream'; if (editorFile === null) { throw "You should save your file before using this action"; } // locate real image path var realImgPath = file.locateFile(editorFile, imgPath); if (realImgPath === null) { throw "Can't find " + imgPath + ' file'; } file.read(realImgPath, function(err, content) { if (err) { throw 'Unable to read ' + realImgPath + ': ' + err; } var b64 = base64.encode(String(content)); if (!b64) { throw "Can't encode file content to base64"; } b64 = 'data:' + (actionUtils.mimeTypes[String(file.getExt(realImgPath))] || defaultMimeType) + ';base64,' + b64; editor.replaceContent('$0' + b64, pos, pos + imgPath.length); }); return true; } /** * Decodes base64 string back to file. * @param {IEmmetEditor} editor * @param {String} data Base64-encoded file content * @param {Number} pos Caret position where image is located in the editor */ function decodeFromBase64(editor, data, pos) { // ask user to enter path to file var filePath = String(editor.prompt('Enter path to file (absolute or relative)')); if (!filePath) return false; var absPath = file.createPath(editor.getFilePath(), filePath); if (!absPath) { throw "Can't save file"; } file.save(absPath, base64.decode( data.replace(/^data\:.+?;.+?,/, '') )); editor.replaceContent('$0' + filePath, pos, pos + data.length); return true; } return { /** * Action to encode or decode file to data:url * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ encodeDecodeDataUrlAction: function(editor) { var data = String(editor.getSelection()); var caretPos = editor.getCaretPos(); var info = editorUtils.outputInfo(editor); if (!data) { // no selection, try to find image bounds from current caret position var text = info.content, m; while (caretPos-- >= 0) { if (startsWith('src=', text, caretPos)) { // found <img src=""> if ((m = text.substr(caretPos).match(/^(src=(["'])?)([^'"<>\s]+)\1?/))) { data = m[3]; caretPos += m[1].length; } break; } else if (startsWith('url(', text, caretPos)) { // found CSS url() pattern if ((m = text.substr(caretPos).match(/^(url\((['"])?)([^'"\)\s]+)\1?/))) { data = m[3]; caretPos += m[1].length; } break; } } } if (data) { if (startsWith('data:', data)) { return decodeFromBase64(editor, data, caretPos); } else { return encodeToBase64(editor, data, caretPos); } } return false; } }; }); },{"../plugin/file":"plugin/file.js","../utils/action":"utils/action.js","../utils/base64":"utils/base64.js","../utils/editor":"utils/editor.js"}],"action/editPoints.js":[function(require,module,exports){ /** * Move between next/prev edit points. 'Edit points' are places between tags * and quotes of empty attributes in html */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { /** * Search for new caret insertion point * @param {IEmmetEditor} editor Editor instance * @param {Number} inc Search increment: -1 — search left, 1 — search right * @param {Number} offset Initial offset relative to current caret position * @return {Number} Returns -1 if insertion point wasn't found */ function findNewEditPoint(editor, inc, offset) { inc = inc || 1; offset = offset || 0; var curPoint = editor.getCaretPos() + offset; var content = String(editor.getContent()); var maxLen = content.length; var nextPoint = -1; var reEmptyLine = /^\s+$/; function getLine(ix) { var start = ix; while (start >= 0) { var c = content.charAt(start); if (c == '\n' || c == '\r') break; start--; } return content.substring(start, ix); } while (curPoint <= maxLen && curPoint >= 0) { curPoint += inc; var curChar = content.charAt(curPoint); var nextChar = content.charAt(curPoint + 1); var prevChar = content.charAt(curPoint - 1); switch (curChar) { case '"': case '\'': if (nextChar == curChar && prevChar == '=') { // empty attribute nextPoint = curPoint + 1; } break; case '>': if (nextChar == '<') { // between tags nextPoint = curPoint + 1; } break; case '\n': case '\r': // empty line if (reEmptyLine.test(getLine(curPoint - 1))) { nextPoint = curPoint; } break; } if (nextPoint != -1) break; } return nextPoint; } return { /** * Move to previous edit point * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ previousEditPointAction: function(editor, syntax, profile) { var curPos = editor.getCaretPos(); var newPoint = findNewEditPoint(editor, -1); if (newPoint == curPos) // we're still in the same point, try searching from the other place newPoint = findNewEditPoint(editor, -1, -2); if (newPoint != -1) { editor.setCaretPos(newPoint); return true; } return false; }, /** * Move to next edit point * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ nextEditPointAction: function(editor, syntax, profile) { var newPoint = findNewEditPoint(editor, 1); if (newPoint != -1) { editor.setCaretPos(newPoint); return true; } return false; } }; }); },{}],"action/evaluateMath.js":[function(require,module,exports){ /** * Evaluates simple math expression under caret */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var actionUtils = require('../utils/action'); var utils = require('../utils/common'); var math = require('../utils/math'); var range = require('../assets/range'); return { /** * Evaluates math expression under the caret * @param {IEmmetEditor} editor * @return {Boolean} */ evaluateMathAction: function(editor) { var content = editor.getContent(); var chars = '.+-*/\\'; /** @type Range */ var sel = range(editor.getSelectionRange()); if (!sel.length()) { sel = actionUtils.findExpressionBounds(editor, function(ch) { return utils.isNumeric(ch) || chars.indexOf(ch) != -1; }); } if (sel && sel.length()) { var expr = sel.substring(content); // replace integral division: 11\2 => Math.round(11/2) expr = expr.replace(/([\d\.\-]+)\\([\d\.\-]+)/g, 'round($1/$2)'); try { var result = utils.prettifyNumber(math.evaluate(expr)); editor.replaceContent(result, sel.start, sel.end); editor.setCaretPos(sel.start + result.length); return true; } catch (e) {} } return false; } }; }); },{"../assets/range":"assets/range.js","../utils/action":"utils/action.js","../utils/common":"utils/common.js","../utils/math":"utils/math.js"}],"action/expandAbbreviation.js":[function(require,module,exports){ /** * 'Expand abbreviation' editor action: extracts abbreviation from current caret * position and replaces it with formatted output. * <br><br> * This behavior can be overridden with custom handlers which can perform * different actions when 'Expand Abbreviation' action is called. * For example, a CSS gradient handler that produces vendor-prefixed gradient * definitions registers its own expand abbreviation handler. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var handlerList = require('../assets/handlerList'); var range = require('../assets/range'); var prefs = require('../assets/preferences'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var cssGradient = require('../resolver/cssGradient'); var parser = require('../parser/abbreviation'); /** * Search for abbreviation in editor from current caret position * @param {IEmmetEditor} editor Editor instance * @return {String} */ function findAbbreviation(editor) { var r = range(editor.getSelectionRange()); var content = String(editor.getContent()); if (r.length()) { // abbreviation is selected by user return r.substring(content); } // search for new abbreviation from current caret position var curLine = editor.getCurrentLineRange(); return actionUtils.extractAbbreviation(content.substring(curLine.start, r.start)); } /** * @type HandlerList List of registered handlers */ var handlers = handlerList.create(); // XXX setup default expand handlers /** * Extracts abbreviation from current caret * position and replaces it with formatted output * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Syntax type (html, css, etc.) * @param {String} profile Output profile name (html, xml, xhtml) * @return {Boolean} Returns <code>true</code> if abbreviation was expanded * successfully */ handlers.add(function(editor, syntax, profile) { var caretPos = editor.getSelectionRange().end; var abbr = findAbbreviation(editor); if (abbr) { var content = parser.expand(abbr, { syntax: syntax, profile: profile, contextNode: actionUtils.captureContext(editor) }); if (content) { var replaceFrom = caretPos - abbr.length; var replaceTo = caretPos; // a special case for CSS: if editor already contains // semicolon right after current caret position — replace it too var cssSyntaxes = prefs.getArray('css.syntaxes'); if (cssSyntaxes && ~cssSyntaxes.indexOf(syntax)) { var curContent = editor.getContent(); if (curContent.charAt(caretPos) == ';' && content.charAt(content.length - 1) == ';') { replaceTo++; } } editor.replaceContent(content, replaceFrom, replaceTo); return true; } } return false; }, {order: -1}); handlers.add(cssGradient.expandAbbreviationHandler.bind(cssGradient)); return { /** * The actual “Expand Abbreviation“ action routine * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ expandAbbreviationAction: function(editor, syntax, profile) { var args = utils.toArray(arguments); // normalize incoming arguments var info = editorUtils.outputInfo(editor, syntax, profile); args[1] = info.syntax; args[2] = info.profile; return handlers.exec(false, args); }, /** * A special case of “Expand Abbreviation“ action, invoked by Tab key. * In this case if abbreviation wasn’t expanded successfully or there’s a selecetion, * the current line/selection will be indented. * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ expandAbbreviationWithTabAction: function(editor, syntax, profile) { var sel = editor.getSelection(); var indent = '\t'; // if something is selected in editor, // we should indent the selected content if (sel) { var selRange = range(editor.getSelectionRange()); var content = utils.padString(sel, indent); editor.replaceContent(indent + '${0}', editor.getCaretPos()); var replaceRange = range(editor.getCaretPos(), selRange.length()); editor.replaceContent(content, replaceRange.start, replaceRange.end, true); editor.createSelection(replaceRange.start, replaceRange.start + content.length); return true; } // nothing selected, try to expand if (!this.expandAbbreviationAction(editor, syntax, profile)) { editor.replaceContent(indent, editor.getCaretPos()); } return true; }, _defaultHandler: function(editor, syntax, profile) { var caretPos = editor.getSelectionRange().end; var abbr = this.findAbbreviation(editor); if (abbr) { var ctx = actionUtils.captureContext(editor); var content = parser.expand(abbr, syntax, profile, ctx); if (content) { editor.replaceContent(content, caretPos - abbr.length, caretPos); return true; } } return false; }, /** * Adds custom expand abbreviation handler. The passed function should * return <code>true</code> if it was performed successfully, * <code>false</code> otherwise. * * Added handlers will be called when 'Expand Abbreviation' is called * in order they were added * @memberOf expandAbbreviation * @param {Function} fn * @param {Object} options */ addHandler: function(fn, options) { handlers.add(fn, options); }, /** * Removes registered handler * @returns */ removeHandler: function(fn) { handlers.remove(fn); }, findAbbreviation: findAbbreviation }; }); },{"../assets/handlerList":"assets/handlerList.js","../assets/preferences":"assets/preferences.js","../assets/range":"assets/range.js","../parser/abbreviation":"parser/abbreviation.js","../resolver/cssGradient":"resolver/cssGradient.js","../utils/action":"utils/action.js","../utils/common":"utils/common.js","../utils/editor":"utils/editor.js"}],"action/incrementDecrement.js":[function(require,module,exports){ /** * Increment/decrement number under cursor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var actionUtils = require('../utils/action'); /** * Returns length of integer part of number * @param {String} num */ function intLength(num) { num = num.replace(/^\-/, ''); if (~num.indexOf('.')) { return num.split('.')[0].length; } return num.length; } return { increment01Action: function(editor) { return this.incrementNumber(editor, .1); }, increment1Action: function(editor) { return this.incrementNumber(editor, 1); }, increment10Action: function(editor) { return this.incrementNumber(editor, 10); }, decrement01Action: function(editor) { return this.incrementNumber(editor, -.1); }, decrement1Action: function(editor) { return this.incrementNumber(editor, -1); }, decrement10Action: function(editor) { return this.incrementNumber(editor, -10); }, /** * Default method to increment/decrement number under * caret with given step * @param {IEmmetEditor} editor * @param {Number} step * @return {Boolean} */ incrementNumber: function(editor, step) { var hasSign = false; var hasDecimal = false; var r = actionUtils.findExpressionBounds(editor, function(ch, pos, content) { if (utils.isNumeric(ch)) return true; if (ch == '.') { // make sure that next character is numeric too if (!utils.isNumeric(content.charAt(pos + 1))) return false; return hasDecimal ? false : hasDecimal = true; } if (ch == '-') return hasSign ? false : hasSign = true; return false; }); if (r && r.length()) { var strNum = r.substring(String(editor.getContent())); var num = parseFloat(strNum); if (!isNaN(num)) { num = utils.prettifyNumber(num + step); // do we have zero-padded number? if (/^(\-?)0+[1-9]/.test(strNum)) { var minus = ''; if (RegExp.$1) { minus = '-'; num = num.substring(1); } var parts = num.split('.'); parts[0] = utils.zeroPadString(parts[0], intLength(strNum)); num = minus + parts.join('.'); } editor.replaceContent(num, r.start, r.end); editor.createSelection(r.start, r.start + num.length); return true; } } return false; } }; }); },{"../utils/action":"utils/action.js","../utils/common":"utils/common.js"}],"action/lineBreaks.js":[function(require,module,exports){ /** * Actions to insert line breaks. Some simple editors (like browser's * &lt;textarea&gt;, for example) do not provide such simple things */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var utils = require('../utils/common'); var resources = require('../assets/resources'); var htmlMatcher = require('../assets/htmlMatcher'); var editorUtils = require('../utils/editor'); var xmlSyntaxes = ['html', 'xml', 'xsl']; // setup default preferences 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.'); return { /** * Inserts newline character with proper indentation. This action is used in * editors that doesn't have indentation control (like textarea element) to * provide proper indentation for inserted newlines * @param {IEmmetEditor} editor Editor instance */ insertLineBreakAction: function(editor) { if (!this.insertLineBreakOnlyAction(editor)) { var curPadding = editorUtils.getCurrentLinePadding(editor); var content = String(editor.getContent()); var caretPos = editor.getCaretPos(); var len = content.length; var nl = '\n'; // check out next line padding var lineRange = editor.getCurrentLineRange(); var nextPadding = ''; for (var i = lineRange.end + 1, ch; i < len; i++) { ch = content.charAt(i); if (ch == ' ' || ch == '\t') nextPadding += ch; else break; } if (nextPadding.length > curPadding.length) { editor.replaceContent(nl + nextPadding, caretPos, caretPos, true); } else { editor.replaceContent(nl, caretPos); } } return true; }, /** * Inserts newline character with proper indentation in specific positions only. * @param {IEmmetEditor} editor * @return {Boolean} Returns <code>true</code> if line break was inserted */ insertLineBreakOnlyAction: function(editor) { var info = editorUtils.outputInfo(editor); var caretPos = editor.getCaretPos(); var nl = '\n'; var pad = '\t'; if (~xmlSyntaxes.indexOf(info.syntax)) { // let's see if we're breaking newly created tag var tag = htmlMatcher.tag(info.content, caretPos); if (tag && !tag.innerRange.length()) { editor.replaceContent(nl + pad + utils.getCaretPlaceholder() + nl, caretPos); return true; } } else if (info.syntax == 'css') { /** @type String */ var content = info.content; if (caretPos && content.charAt(caretPos - 1) == '{') { var append = prefs.get('css.closeBraceIndentation'); var hasCloseBrace = content.charAt(caretPos) == '}'; if (!hasCloseBrace) { // do we really need special formatting here? // check if this is really a newly created rule, // look ahead for a closing brace for (var i = caretPos, il = content.length, ch; i < il; i++) { ch = content.charAt(i); if (ch == '{') { // ok, this is a new rule without closing brace break; } if (ch == '}') { // not a new rule, just add indentation append = ''; hasCloseBrace = true; break; } } } if (!hasCloseBrace) { append += '}'; } // defining rule set var insValue = nl + pad + utils.getCaretPlaceholder() + append; editor.replaceContent(insValue, caretPos); return true; } } return false; } }; }); },{"../assets/htmlMatcher":"assets/htmlMatcher.js","../assets/preferences":"assets/preferences.js","../assets/resources":"assets/resources.js","../utils/common":"utils/common.js","../utils/editor":"utils/editor.js"}],"action/main.js":[function(require,module,exports){ /** * Module describes and performs Emmet actions. The actions themselves are * defined in <i>actions</i> folder */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); // all registered actions var actions = {}; // load all default actions var actionModules = { base64: require('./base64'), editPoints: require('./editPoints'), evaluateMath: require('./evaluateMath'), expandAbbreviation: require('./expandAbbreviation'), incrementDecrement: require('./incrementDecrement'), lineBreaks: require('./lineBreaks'), balance: require('./balance'), mergeLines: require('./mergeLines'), reflectCSSValue: require('./reflectCSSValue'), removeTag: require('./removeTag'), selectItem: require('./selectItem'), selectLine: require('./selectLine'), splitJoinTag: require('./splitJoinTag'), toggleComment: require('./toggleComment'), updateImageSize: require('./updateImageSize'), wrapWithAbbreviation: require('./wrapWithAbbreviation'), updateTag: require('./updateTag') }; function addAction(name, fn, options) { name = name.toLowerCase(); options = options || {}; if (typeof options === 'string') { options = {label: options}; } if (!options.label) { options.label = humanizeActionName(name); } actions[name] = { name: name, fn: fn, options: options }; } /** * “Humanizes” action name, makes it more readable for people * @param {String} name Action name (like 'expand_abbreviation') * @return Humanized name (like 'Expand Abbreviation') */ function humanizeActionName(name) { return utils.trim(name.charAt(0).toUpperCase() + name.substring(1).replace(/_[a-z]/g, function(str) { return ' ' + str.charAt(1).toUpperCase(); })); } var bind = function(name, method) { var m = actionModules[name]; return m[method].bind(m); }; // XXX register default actions addAction('encode_decode_data_url', bind('base64', 'encodeDecodeDataUrlAction'), 'Encode\\Decode data:URL image'); addAction('prev_edit_point', bind('editPoints', 'previousEditPointAction'), 'Previous Edit Point'); addAction('next_edit_point', bind('editPoints', 'nextEditPointAction'), 'Next Edit Point'); addAction('evaluate_math_expression', bind('evaluateMath', 'evaluateMathAction'), 'Numbers/Evaluate Math Expression'); addAction('expand_abbreviation_with_tab', bind('expandAbbreviation', 'expandAbbreviationWithTabAction'), {hidden: true}); addAction('expand_abbreviation', bind('expandAbbreviation', 'expandAbbreviationAction'), 'Expand Abbreviation'); addAction('insert_formatted_line_break_only', bind('lineBreaks', 'insertLineBreakOnlyAction'), {hidden: true}); addAction('insert_formatted_line_break', bind('lineBreaks', 'insertLineBreakAction'), {hidden: true}); addAction('balance_inward', bind('balance', 'balanceInwardAction'), 'Balance (inward)'); addAction('balance_outward', bind('balance', 'balanceOutwardAction'), 'Balance (outward)'); addAction('matching_pair', bind('balance', 'goToMatchingPairAction'), 'HTML/Go To Matching Tag Pair'); addAction('merge_lines', bind('mergeLines', 'mergeLinesAction'), 'Merge Lines'); addAction('reflect_css_value', bind('reflectCSSValue', 'reflectCSSValueAction'), 'CSS/Reflect Value'); addAction('remove_tag', bind('removeTag', 'removeTagAction'), 'HTML/Remove Tag'); addAction('select_next_item', bind('selectItem', 'selectNextItemAction'), 'Select Next Item'); addAction('select_previous_item', bind('selectItem', 'selectPreviousItemAction'), 'Select Previous Item'); addAction('split_join_tag', bind('splitJoinTag', 'splitJoinTagAction'), 'HTML/Split\\Join Tag Declaration'); addAction('toggle_comment', bind('toggleComment', 'toggleCommentAction'), 'Toggle Comment'); addAction('update_image_size', bind('updateImageSize', 'updateImageSizeAction'), 'Update Image Size'); addAction('wrap_with_abbreviation', bind('wrapWithAbbreviation', 'wrapWithAbbreviationAction'), 'Wrap With Abbreviation'); addAction('update_tag', bind('updateTag', 'updateTagAction'), 'HTML/Update Tag'); [1, -1, 10, -10, 0.1, -0.1].forEach(function(num) { var prefix = num > 0 ? 'increment' : 'decrement'; var suffix = String(Math.abs(num)).replace('.', '').substring(0, 2); var actionId = prefix + '_number_by_' + suffix; var actionMethod = prefix + suffix + 'Action'; var actionLabel = 'Numbers/' + prefix.charAt(0).toUpperCase() + prefix.substring(1) + ' number by ' + Math.abs(num); addAction(actionId, bind('incrementDecrement', actionMethod), actionLabel); }); return { /** * Registers new action * @param {String} name Action name * @param {Function} fn Action function * @param {Object} options Custom action options:<br> * <b>label</b> : (<code>String</code>) – Human-readable action name. * May contain '/' symbols as submenu separators<br> * <b>hidden</b> : (<code>Boolean</code>) – Indicates whether action * should be displayed in menu (<code>getMenu()</code> method) */ add: addAction, /** * Returns action object * @param {String} name Action name * @returns {Object} */ get: function(name) { return actions[name.toLowerCase()]; }, /** * Runs Emmet action. For list of available actions and their * arguments see <i>actions</i> folder. * @param {String} name Action name * @param {Array} args Additional arguments. It may be array of arguments * or inline arguments. The first argument should be <code>IEmmetEditor</code> instance * @returns {Boolean} Status of performed operation, <code>true</code> * means action was performed successfully. * @example * require('action/main').run('expand_abbreviation', editor); * require('action/main').run('wrap_with_abbreviation', [editor, 'div']); */ run: function(name, args) { if (!Array.isArray(args)) { args = utils.toArray(arguments, 1); } var action = this.get(name); if (!action) { throw new Error('Action "' + name + '" is not defined'); } return action.fn.apply(action, args); }, /** * Returns all registered actions as object * @returns {Object} */ getAll: function() { return actions; }, /** * Returns all registered actions as array * @returns {Array} */ getList: function() { var all = this.getAll(); return Object.keys(all).map(function(key) { return all[key]; }); }, /** * Returns actions list as structured menu. If action has <i>label</i>, * it will be splitted by '/' symbol into submenus (for example: * CSS/Reflect Value) and grouped with other items * @param {Array} skipActions List of action identifiers that should be * skipped from menu * @returns {Array} */ getMenu: function(skipActions) { var result = []; skipActions = skipActions || []; this.getList().forEach(function(action) { if (action.options.hidden || ~skipActions.indexOf(action.name)) return; var actionName = humanizeActionName(action.name); var ctx = result; if (action.options.label) { var parts = action.options.label.split('/'); actionName = parts.pop(); // create submenus, if needed var menuName, submenu; while ((menuName = parts.shift())) { submenu = utils.find(ctx, function(item) { return item.type == 'submenu' && item.name == menuName; }); if (!submenu) { submenu = { name: menuName, type: 'submenu', items: [] }; ctx.push(submenu); } ctx = submenu.items; } } ctx.push({ type: 'action', name: action.name, label: actionName }); }); return result; }, /** * Returns action name associated with menu item title * @param {String} title * @returns {String} */ getActionNameForMenuTitle: function(title, menu) { return utils.find(menu || this.getMenu(), function(val) { if (val.type == 'action') { if (val.label == title || val.name == title) { return val.name; } } else { return this.getActionNameForMenuTitle(title, val.items); } }, this); } }; }); },{"../utils/common":"utils/common.js","./balance":"action/balance.js","./base64":"action/base64.js","./editPoints":"action/editPoints.js","./evaluateMath":"action/evaluateMath.js","./expandAbbreviation":"action/expandAbbreviation.js","./incrementDecrement":"action/incrementDecrement.js","./lineBreaks":"action/lineBreaks.js","./mergeLines":"action/mergeLines.js","./reflectCSSValue":"action/reflectCSSValue.js","./removeTag":"action/removeTag.js","./selectItem":"action/selectItem.js","./selectLine":"action/selectLine.js","./splitJoinTag":"action/splitJoinTag.js","./toggleComment":"action/toggleComment.js","./updateImageSize":"action/updateImageSize.js","./updateTag":"action/updateTag.js","./wrapWithAbbreviation":"action/wrapWithAbbreviation.js"}],"action/mergeLines.js":[function(require,module,exports){ /** * Merges selected lines or lines between XHTML tag pairs * @param {Function} require * @param {Underscore} _ */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports,