(function(){ /** * Zen Coding settings * @author Sergey Chikuyonok (serge.che@gmail.com) * @link http://chikuyonok.ru */ var zen_settings = { /** * Variables that can be placed inside snippets or abbreviations as ${variable} * ${child} variable is reserved, don't use it */ 'variables': { 'lang': 'en', 'locale': 'en-US', 'charset': 'UTF-8', 'profile': 'xhtml', /** Inner element indentation */ 'indentation': '\t' // TODO take from Aptana settings }, 'css': { 'snippets': { "@i": "@import url(|);", "@m": "@media print {\n\t|\n}", "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}", "!": "!important", "pos": "position:|;", "pos:s": "position:static;", "pos:a": "position:absolute;", "pos:r": "position:relative;", "pos:f": "position:fixed;", "t": "top:|;", "t:a": "top:auto;", "r": "right:|;", "r:a": "right:auto;", "b": "bottom:|;", "b:a": "bottom:auto;", "l": "left:|;", "l:a": "left:auto;", "z": "z-index:|;", "z:a": "z-index:auto;", "fl": "float:|;", "fl:n": "float:none;", "fl:l": "float:left;", "fl:r": "float:right;", "cl": "clear:|;", "cl:n": "clear:none;", "cl:l": "clear:left;", "cl:r": "clear:right;", "cl:b": "clear:both;", "d": "display:|;", "d:n": "display:none;", "d:b": "display:block;", "d:ib": "display:inline;", "d:li": "display:list-item;", "d:ri": "display:run-in;", "d:cp": "display:compact;", "d:tb": "display:table;", "d:itb": "display:inline-table;", "d:tbcp": "display:table-caption;", "d:tbcl": "display:table-column;", "d:tbclg": "display:table-column-group;", "d:tbhg": "display:table-header-group;", "d:tbfg": "display:table-footer-group;", "d:tbr": "display:table-row;", "d:tbrg": "display:table-row-group;", "d:tbc": "display:table-cell;", "d:rb": "display:ruby;", "d:rbb": "display:ruby-base;", "d:rbbg": "display:ruby-base-group;", "d:rbt": "display:ruby-text;", "d:rbtg": "display:ruby-text-group;", "v": "visibility:|;", "v:v": "visibility:visible;", "v:h": "visibility:hidden;", "v:c": "visibility:collapse;", "ov": "overflow:|;", "ov:v": "overflow:visible;", "ov:h": "overflow:hidden;", "ov:s": "overflow:scroll;", "ov:a": "overflow:auto;", "ovx": "overflow-x:|;", "ovx:v": "overflow-x:visible;", "ovx:h": "overflow-x:hidden;", "ovx:s": "overflow-x:scroll;", "ovx:a": "overflow-x:auto;", "ovy": "overflow-y:|;", "ovy:v": "overflow-y:visible;", "ovy:h": "overflow-y:hidden;", "ovy:s": "overflow-y:scroll;", "ovy:a": "overflow-y:auto;", "ovs": "overflow-style:|;", "ovs:a": "overflow-style:auto;", "ovs:s": "overflow-style:scrollbar;", "ovs:p": "overflow-style:panner;", "ovs:m": "overflow-style:move;", "ovs:mq": "overflow-style:marquee;", "zoo": "zoom:1;", "cp": "clip:|;", "cp:a": "clip:auto;", "cp:r": "clip:rect(|);", "bxz": "box-sizing:|;", "bxz:cb": "box-sizing:content-box;", "bxz:bb": "box-sizing:border-box;", "bxsh": "box-shadow:|;", "bxsh:n": "box-shadow:none;", "bxsh:w": "-webkit-box-shadow:0 0 0 #000;", "bxsh:m": "-moz-box-shadow:0 0 0 0 #000;", "m": "margin:|;", "m:a": "margin:auto;", "m:0": "margin:0;", "m:2": "margin:0 0;", "m:3": "margin:0 0 0;", "m:4": "margin:0 0 0 0;", "mt": "margin-top:|;", "mt:a": "margin-top:auto;", "mr": "margin-right:|;", "mr:a": "margin-right:auto;", "mb": "margin-bottom:|;", "mb:a": "margin-bottom:auto;", "ml": "margin-left:|;", "ml:a": "margin-left:auto;", "p": "padding:|;", "p:0": "padding:0;", "p:2": "padding:0 0;", "p:3": "padding:0 0 0;", "p:4": "padding:0 0 0 0;", "pt": "padding-top:|;", "pr": "padding-right:|;", "pb": "padding-bottom:|;", "pl": "padding-left:|;", "w": "width:|;", "w:a": "width:auto;", "h": "height:|;", "h:a": "height:auto;", "maw": "max-width:|;", "maw:n": "max-width:none;", "mah": "max-height:|;", "mah:n": "max-height:none;", "miw": "min-width:|;", "mih": "min-height:|;", "o": "outline:|;", "o:n": "outline:none;", "oo": "outline-offset:|;", "ow": "outline-width:|;", "os": "outline-style:|;", "oc": "outline-color:#000;", "oc:i": "outline-color:invert;", "bd": "border:|;", "bd+": "border:1px solid #000;", "bd:n": "border:none;", "bdbk": "border-break:|;", "bdbk:c": "border-break:close;", "bdcl": "border-collapse:|;", "bdcl:c": "border-collapse:collapse;", "bdcl:s": "border-collapse:separate;", "bdc": "border-color:#000;", "bdi": "border-image:url(|);", "bdi:n": "border-image:none;", "bdi:w": "-webkit-border-image:url(|) 0 0 0 0 stretch stretch;", "bdi:m": "-moz-border-image:url(|) 0 0 0 0 stretch stretch;", "bdti": "border-top-image:url(|);", "bdti:n": "border-top-image:none;", "bdri": "border-right-image:url(|);", "bdri:n": "border-right-image:none;", "bdbi": "border-bottom-image:url(|);", "bdbi:n": "border-bottom-image:none;", "bdli": "border-left-image:url(|);", "bdli:n": "border-left-image:none;", "bdci": "border-corner-image:url(|);", "bdci:n": "border-corner-image:none;", "bdci:c": "border-corner-image:continue;", "bdtli": "border-top-left-image:url(|);", "bdtli:n": "border-top-left-image:none;", "bdtli:c": "border-top-left-image:continue;", "bdtri": "border-top-right-image:url(|);", "bdtri:n": "border-top-right-image:none;", "bdtri:c": "border-top-right-image:continue;", "bdbri": "border-bottom-right-image:url(|);", "bdbri:n": "border-bottom-right-image:none;", "bdbri:c": "border-bottom-right-image:continue;", "bdbli": "border-bottom-left-image:url(|);", "bdbli:n": "border-bottom-left-image:none;", "bdbli:c": "border-bottom-left-image:continue;", "bdf": "border-fit:|;", "bdf:c": "border-fit:clip;", "bdf:r": "border-fit:repeat;", "bdf:sc": "border-fit:scale;", "bdf:st": "border-fit:stretch;", "bdf:ow": "border-fit:overwrite;", "bdf:of": "border-fit:overflow;", "bdf:sp": "border-fit:space;", "bdl": "border-length:|;", "bdl:a": "border-length:auto;", "bdsp": "border-spacing:|;", "bds": "border-style:|;", "bds:n": "border-style:none;", "bds:h": "border-style:hidden;", "bds:dt": "border-style:dotted;", "bds:ds": "border-style:dashed;", "bds:s": "border-style:solid;", "bds:db": "border-style:double;", "bds:dtds": "border-style:dot-dash;", "bds:dtdtds": "border-style:dot-dot-dash;", "bds:w": "border-style:wave;", "bds:g": "border-style:groove;", "bds:r": "border-style:ridge;", "bds:i": "border-style:inset;", "bds:o": "border-style:outset;", "bdw": "border-width:|;", "bdt": "border-top:|;", "bdt+": "border-top:1px solid #000;", "bdt:n": "border-top:none;", "bdtw": "border-top-width:|;", "bdts": "border-top-style:|;", "bdts:n": "border-top-style:none;", "bdtc": "border-top-color:#000;", "bdr": "border-right:|;", "bdr+": "border-right:1px solid #000;", "bdr:n": "border-right:none;", "bdrw": "border-right-width:|;", "bdrs": "border-right-style:|;", "bdrs:n": "border-right-style:none;", "bdrc": "border-right-color:#000;", "bdb": "border-bottom:|;", "bdb+": "border-bottom:1px solid #000;", "bdb:n": "border-bottom:none;", "bdbw": "border-bottom-width:|;", "bdbs": "border-bottom-style:|;", "bdbs:n": "border-bottom-style:none;", "bdbc": "border-bottom-color:#000;", "bdl": "border-left:|;", "bdl+": "border-left:1px solid #000;", "bdl:n": "border-left:none;", "bdlw": "border-left-width:|;", "bdls": "border-left-style:|;", "bdls:n": "border-left-style:none;", "bdlc": "border-left-color:#000;", "bdrs": "border-radius:|;", "bdtrrs": "border-top-right-radius:|;", "bdtlrs": "border-top-left-radius:|;", "bdbrrs": "border-bottom-right-radius:|;", "bdblrs": "border-bottom-left-radius:|;", "bg": "background:|;", "bg+": "background:#FFF url(|) 0 0 no-repeat;", "bg:n": "background:none;", "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='|x.png');", "bgc": "background-color:#FFF;", "bgi": "background-image:url(|);", "bgi:n": "background-image:none;", "bgr": "background-repeat:|;", "bgr:n": "background-repeat:no-repeat;", "bgr:x": "background-repeat:repeat-x;", "bgr:y": "background-repeat:repeat-y;", "bga": "background-attachment:|;", "bga:f": "background-attachment:fixed;", "bga:s": "background-attachment:scroll;", "bgp": "background-position:0 0;", "bgpx": "background-position-x:|;", "bgpy": "background-position-y:|;", "bgbk": "background-break:|;", "bgbk:bb": "background-break:bounding-box;", "bgbk:eb": "background-break:each-box;", "bgbk:c": "background-break:continuous;", "bgcp": "background-clip:|;", "bgcp:bb": "background-clip:border-box;", "bgcp:pb": "background-clip:padding-box;", "bgcp:cb": "background-clip:content-box;", "bgcp:nc": "background-clip:no-clip;", "bgo": "background-origin:|;", "bgo:pb": "background-origin:padding-box;", "bgo:bb": "background-origin:border-box;", "bgo:cb": "background-origin:content-box;", "bgz": "background-size:|;", "bgz:a": "background-size:auto;", "bgz:ct": "background-size:contain;", "bgz:cv": "background-size:cover;", "c": "color:#000;", "tbl": "table-layout:|;", "tbl:a": "table-layout:auto;", "tbl:f": "table-layout:fixed;", "cps": "caption-side:|;", "cps:t": "caption-side:top;", "cps:b": "caption-side:bottom;", "ec": "empty-cells:|;", "ec:s": "empty-cells:show;", "ec:h": "empty-cells:hide;", "lis": "list-style:|;", "lis:n": "list-style:none;", "lisp": "list-style-position:|;", "lisp:i": "list-style-position:inside;", "lisp:o": "list-style-position:outside;", "list": "list-style-type:|;", "list:n": "list-style-type:none;", "list:d": "list-style-type:disc;", "list:c": "list-style-type:circle;", "list:s": "list-style-type:square;", "list:dc": "list-style-type:decimal;", "list:dclz": "list-style-type:decimal-leading-zero;", "list:lr": "list-style-type:lower-roman;", "list:ur": "list-style-type:upper-roman;", "lisi": "list-style-image:|;", "lisi:n": "list-style-image:none;", "q": "quotes:|;", "q:n": "quotes:none;", "q:ru": "quotes:'\00AB' '\00BB' '\201E' '\201C';", "q:en": "quotes:'\201C' '\201D' '\2018' '\2019';", "ct": "content:|;", "ct:n": "content:normal;", "ct:oq": "content:open-quote;", "ct:noq": "content:no-open-quote;", "ct:cq": "content:close-quote;", "ct:ncq": "content:no-close-quote;", "ct:a": "content:attr(|);", "ct:c": "content:counter(|);", "ct:cs": "content:counters(|);", "coi": "counter-increment:|;", "cor": "counter-reset:|;", "va": "vertical-align:|;", "va:sup": "vertical-align:super;", "va:t": "vertical-align:top;", "va:tt": "vertical-align:text-top;", "va:m": "vertical-align:middle;", "va:bl": "vertical-align:baseline;", "va:b": "vertical-align:bottom;", "va:tb": "vertical-align:text-bottom;", "va:sub": "vertical-align:sub;", "ta": "text-align:|;", "ta:l": "text-align:left;", "ta:c": "text-align:center;", "ta:r": "text-align:right;", "tal": "text-align-last:|;", "tal:a": "text-align-last:auto;", "tal:l": "text-align-last:left;", "tal:c": "text-align-last:center;", "tal:r": "text-align-last:right;", "td": "text-decoration:|;", "td:n": "text-decoration:none;", "td:u": "text-decoration:underline;", "td:o": "text-decoration:overline;", "td:l": "text-decoration:line-through;", "te": "text-emphasis:|;", "te:n": "text-emphasis:none;", "te:ac": "text-emphasis:accent;", "te:dt": "text-emphasis:dot;", "te:c": "text-emphasis:circle;", "te:ds": "text-emphasis:disc;", "te:b": "text-emphasis:before;", "te:a": "text-emphasis:after;", "th": "text-height:|;", "th:a": "text-height:auto;", "th:f": "text-height:font-size;", "th:t": "text-height:text-size;", "th:m": "text-height:max-size;", "ti": "text-indent:|;", "ti:-": "text-indent:-9999px;", "tj": "text-justify:|;", "tj:a": "text-justify:auto;", "tj:iw": "text-justify:inter-word;", "tj:ii": "text-justify:inter-ideograph;", "tj:ic": "text-justify:inter-cluster;", "tj:d": "text-justify:distribute;", "tj:k": "text-justify:kashida;", "tj:t": "text-justify:tibetan;", "to": "text-outline:|;", "to+": "text-outline:0 0 #000;", "to:n": "text-outline:none;", "tr": "text-replace:|;", "tr:n": "text-replace:none;", "tt": "text-transform:|;", "tt:n": "text-transform:none;", "tt:c": "text-transform:capitalize;", "tt:u": "text-transform:uppercase;", "tt:l": "text-transform:lowercase;", "tw": "text-wrap:|;", "tw:n": "text-wrap:normal;", "tw:no": "text-wrap:none;", "tw:u": "text-wrap:unrestricted;", "tw:s": "text-wrap:suppress;", "tsh": "text-shadow:|;", "tsh+": "text-shadow:0 0 0 #000;", "tsh:n": "text-shadow:none;", "lh": "line-height:|;", "whs": "white-space:|;", "whs:n": "white-space:normal;", "whs:p": "white-space:pre;", "whs:nw": "white-space:nowrap;", "whs:pw": "white-space:pre-wrap;", "whs:pl": "white-space:pre-line;", "whsc": "white-space-collapse:|;", "whsc:n": "white-space-collapse:normal;", "whsc:k": "white-space-collapse:keep-all;", "whsc:l": "white-space-collapse:loose;", "whsc:bs": "white-space-collapse:break-strict;", "whsc:ba": "white-space-collapse:break-all;", "wob": "word-break:|;", "wob:n": "word-break:normal;", "wob:k": "word-break:keep-all;", "wob:l": "word-break:loose;", "wob:bs": "word-break:break-strict;", "wob:ba": "word-break:break-all;", "wos": "word-spacing:|;", "wow": "word-wrap:|;", "wow:nm": "word-wrap:normal;", "wow:n": "word-wrap:none;", "wow:u": "word-wrap:unrestricted;", "wow:s": "word-wrap:suppress;", "lts": "letter-spacing:|;", "f": "font:|;", "f+": "font:1em Arial,sans-serif;", "fw": "font-weight:|;", "fw:n": "font-weight:normal;", "fw:b": "font-weight:bold;", "fw:br": "font-weight:bolder;", "fw:lr": "font-weight:lighter;", "fs": "font-style:|;", "fs:n": "font-style:normal;", "fs:i": "font-style:italic;", "fs:o": "font-style:oblique;", "fv": "font-variant:|;", "fv:n": "font-variant:normal;", "fv:sc": "font-variant:small-caps;", "fz": "font-size:|;", "fza": "font-size-adjust:|;", "fza:n": "font-size-adjust:none;", "ff": "font-family:|;", "ff:s": "font-family:serif;", "ff:ss": "font-family:sans-serif;", "ff:c": "font-family:cursive;", "ff:f": "font-family:fantasy;", "ff:m": "font-family:monospace;", "fef": "font-effect:|;", "fef:n": "font-effect:none;", "fef:eg": "font-effect:engrave;", "fef:eb": "font-effect:emboss;", "fef:o": "font-effect:outline;", "fem": "font-emphasize:|;", "femp": "font-emphasize-position:|;", "femp:b": "font-emphasize-position:before;", "femp:a": "font-emphasize-position:after;", "fems": "font-emphasize-style:|;", "fems:n": "font-emphasize-style:none;", "fems:ac": "font-emphasize-style:accent;", "fems:dt": "font-emphasize-style:dot;", "fems:c": "font-emphasize-style:circle;", "fems:ds": "font-emphasize-style:disc;", "fsm": "font-smooth:|;", "fsm:a": "font-smooth:auto;", "fsm:n": "font-smooth:never;", "fsm:aw": "font-smooth:always;", "fst": "font-stretch:|;", "fst:n": "font-stretch:normal;", "fst:uc": "font-stretch:ultra-condensed;", "fst:ec": "font-stretch:extra-condensed;", "fst:c": "font-stretch:condensed;", "fst:sc": "font-stretch:semi-condensed;", "fst:se": "font-stretch:semi-expanded;", "fst:e": "font-stretch:expanded;", "fst:ee": "font-stretch:extra-expanded;", "fst:ue": "font-stretch:ultra-expanded;", "op": "opacity:|;", "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);", "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';", "rz": "resize:|;", "rz:n": "resize:none;", "rz:b": "resize:both;", "rz:h": "resize:horizontal;", "rz:v": "resize:vertical;", "cur": "cursor:|;", "cur:a": "cursor:auto;", "cur:d": "cursor:default;", "cur:c": "cursor:crosshair;", "cur:ha": "cursor:hand;", "cur:he": "cursor:help;", "cur:m": "cursor:move;", "cur:p": "cursor:pointer;", "cur:t": "cursor:text;", "pgbb": "page-break-before:|;", "pgbb:au": "page-break-before:auto;", "pgbb:al": "page-break-before:always;", "pgbb:l": "page-break-before:left;", "pgbb:r": "page-break-before:right;", "pgbi": "page-break-inside:|;", "pgbi:au": "page-break-inside:auto;", "pgbi:av": "page-break-inside:avoid;", "pgba": "page-break-after:|;", "pgba:au": "page-break-after:auto;", "pgba:al": "page-break-after:always;", "pgba:l": "page-break-after:left;", "pgba:r": "page-break-after:right;", "orp": "orphans:|;", "wid": "widows:|;" } }, 'html': { 'snippets': { 'cc:ie6': '', 'cc:ie': '', 'cc:noie': '\n\t${child}|\n', 'html:4t': '\n' + '\n' + '
\n' + 'html
, starting from
* start_ix
position
* @param {String} html Code to search
* @param {Number} start_ix Character index where to start searching pair
* (commonly, current caret position)
* @param {Function} action Function that creates selection range
* @return {Array|null}
*/
function findPair(html, start_ix, action) {
action = action || makeRange;
var forward_stack = [],
backward_stack = [],
/** @type {tag()} */
opening_tag = null,
/** @type {tag()} */
closing_tag = null,
range = null,
html_len = html.length,
m,
ix,
tmp_tag;
forward_stack.last = backward_stack.last = function() {
return this[this.length - 1];
}
function hasMatch(str, start) {
if (arguments.length == 1)
start = ix;
return html.substr(start, str.length) == str;
}
function searchCommentStart(from) {
while (from--) {
if (html.charAt(from) == '<' && hasMatch('') + ix + 3;
if (ix < start_ix && end_ix >= start_ix)
return saveMatch( comment(ix, end_ix) );
}
} else if (ch == '-' && hasMatch('-->')) { // found comment end
// search left until comment start is reached
ix = searchCommentStart(ix);
}
}
if (!opening_tag)
return action(null);
// find closing tag
if (!closing_tag) {
for (ix = start_ix; ix < html_len; ix++) {
var ch = html.charAt(ix);
if (ch == '<') {
var check_str = html.substring(ix, html_len);
if ( (m = check_str.match(start_tag)) ) { // found opening tag
tmp_tag = tag(m, ix);
if (!tmp_tag.unary)
forward_stack.push( tmp_tag );
} else if ( (m = check_str.match(end_tag)) ) { // found closing tag
var tmp_tag = tag(m, ix);
if (forward_stack.last() && forward_stack.last().name == tmp_tag.name)
forward_stack.pop();
else { // found matched closing tag
closing_tag = tmp_tag;
break;
}
} else if (hasMatch('') + 3;
}
} else if (ch == '-' && hasMatch('-->')) {
// looks like cursor was inside comment with invalid HTML
if (!forward_stack.last() || forward_stack.last().type != 'comment') {
var end_ix = ix + 3;
return action(comment( searchCommentStart(ix), end_ix ));
}
}
}
}
return action(opening_tag, closing_tag, start_ix);
}
/**
* Search for matching tags in html
, starting
* from start_ix
position. The result is automatically saved in
* last_match
property
*
* @return {Array|null}
*/
var HTMLPairMatcher = this.HTMLPairMatcher = function(/* String */ html, /* Number */ start_ix){
return findPair(html, start_ix, saveMatch);
}
HTMLPairMatcher.start_tag = start_tag;
HTMLPairMatcher.end_tag = end_tag;
/**
* Search for matching tags in html
, starting from
* start_ix
position. The difference between
* HTMLPairMatcher
function itself is that find
* method doesn't save matched result in last_match
property.
* This method is generally used for lookups
*/
HTMLPairMatcher.find = function(html, start_ix) {
return findPair(html, start_ix);
};
/**
* Search for matching tags in html
, starting from
* start_ix
position. The difference between
* HTMLPairMatcher
function itself is that getTags
* method doesn't save matched result in last_match
property
* and returns array of opening and closing tags
* This method is generally used for lookups
*/
HTMLPairMatcher.getTags = function(html, start_ix) {
return findPair(html, start_ix, function(opening_tag, closing_tag){
return [opening_tag, closing_tag];
});
};
HTMLPairMatcher.last_match = last_match;
})();/**
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
* @include "settings.js"
* @include "/EclipseMonkey/scripts/monkey-doc.js"
*/
var zen_coding = (function(){
var re_tag = /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/;
var TYPE_ABBREVIATION = 'zen-tag',
TYPE_EXPANDO = 'zen-expando',
/** Reference to another abbreviation or tag */
TYPE_REFERENCE = 'zen-reference',
content_placeholder = '{%::zen-content::%}',
newline = '\n';
var default_profile = {
tag_case: 'lower',
attr_case: 'lower',
attr_quotes: 'double',
// each tag on new line
tag_nl: 'decide',
place_cursor: true,
// indent tags
indent: true,
// use self-closing style for writing empty elements, e.g. remove_empty
to true to filter
* empty lines
* @param {String} text
* @param {Boolean} [remove_empty]
* @return {Array}
*/
function splitByLines(text, remove_empty) {
// IE fails to split string by regexp,
// need to normalize newlines first
var lines = text.replace(/\r\n/g, '\n').replace(/\n\r/g, '\n').split('\n');
// var nl = getNewline(),
// lines = text.split(new RegExp('\\r?\\n|\\n\\r|\\r|' + nl));
if (remove_empty) {
for (var i = lines.length; i >= 0; i--) {
if (!trim(lines[i]))
lines.splice(i, 1);
}
}
return lines;
}
/**
* Trim whitespace from string
* @param {String} text
* @return {String}
*/
function trim(text) {
return (text || "").replace( /^\s+|\s+$/g, "" );
}
function createProfile(options) {
var result = {};
for (var p in default_profile)
result[p] = (p in options) ? options[p] : default_profile[p];
return result;
}
function setupProfile(name, options) {
profiles[name.toLowerCase()] = createProfile(options || {});
}
/**
* Helper function that transforms string into hash
* @return {Object}
*/
function stringToHash(str){
var obj = {}, items = str.split(",");
for ( var i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}
/**
* Отбивает текст отступами
* @param {String} text Текст, который нужно отбить
* @param {String|Number} pad Количество отступов или сам отступ
* @return {String}
*/
function padString(text, pad, verbose) {
var pad_str = '', result = '';
if (typeof(pad) == 'number')
for (var i = 0; i < pad; i++)
pad_str += zen_settings.variables.indentation;
else
pad_str = pad;
var lines = splitByLines(text),
nl = getNewline();
result += lines[0];
for (var j = 1; j < lines.length; j++)
result += nl + pad_str + lines[j];
return result;
}
/**
* Check if passed abbreviation is snippet
* @param {String} abbr
* @param {String} type
* @return {Boolean}
*/
function isShippet(abbr, type) {
return getSnippet(type, abbr) ? true : false;
}
/**
* Проверяет, закачивается ли строка полноценным тэгом. В основном
* используется для проверки принадлежности символа '>' аббревиатуре
* или тэгу
* @param {String} str
* @return {Boolean}
*/
function isEndsWithTag(str) {
return re_tag.test(str);
}
/**
* Returns specified elements collection (like 'empty', 'block_level') from
* resource
. If collections wasn't found, returns empty object
* @param {Object} resource
* @param {String} type
* @return {Object}
*/
function getElementsCollection(resource, type) {
if (resource && resource.element_types)
return resource.element_types[type] || {}
else
return {};
}
/**
* Replace variables like ${var} in string
* @param {String} str
* @param {Object} [vars] Variable set (default is zen_settings.variables
)
* @return {String}
*/
function replaceVariables(str, vars) {
vars = vars || zen_settings.variables;
return str.replace(/\$\{([\w\-]+)\}/g, function(str, p1){
return (p1 in vars) ? vars[p1] : str;
});
}
/**
* Тэг
* @class
* @param {String} name Имя тэга
* @param {Number} count Сколько раз вывести тэг (по умолчанию: 1)
* @param {String} type Тип тэга (html, xml)
*/
function Tag(name, count, type) {
name = name.toLowerCase();
type = type || 'html';
var abbr = getAbbreviation(type, name);
if (abbr && abbr.type == TYPE_REFERENCE)
abbr = getAbbreviation(type, abbr.value);
this.name = (abbr) ? abbr.value.name : name.replace('+', '');
this.count = count || 1;
this.children = [];
this.attributes = [];
this._attr_hash = {};
this._abbr = abbr;
this._res = zen_settings[type];
this._content = '';
this.repeat_by_lines = false;
// add default attributes
if (this._abbr && this._abbr.value.attributes) {
var def_attrs = this._abbr.value.attributes;
if (def_attrs) {
for (var i = 0; i < def_attrs.length; i++) {
var attr = def_attrs[i];
this.addAttribute(attr.name, attr.value);
}
}
}
}
Tag.prototype = {
/**
* Добавляет нового потомка
* @param {Tag} tag
*/
addChild: function(tag) {
this.children.push(tag);
},
/**
* Добавляет атрибут
* @param {String} name Название атрибута
* @param {String} value Значение атрибута
*/
addAttribute: function(name, value) {
var a;
if (name in this._attr_hash) {
// attribute already exists, decide what to do
a = this._attr_hash[name];
if (name == 'class') {
// 'class' is a magic attribute
a.value += ((a.value) ? ' ' : '') + value;
} else {
a.value = value;
}
} else {
a = {name: name, value: value};
this._attr_hash[name] = a
this.attributes.push(a);
}
},
/**
* Проверяет, является ли текущий элемент пустым
* @return {Boolean}
*/
isEmpty: function() {
return (this._abbr && this._abbr.value.is_empty) || (this.name in getElementsCollection(this._res, 'empty'));
},
/**
* Проверяет, является ли текущий элемент строчным
* @return {Boolean}
*/
isInline: function() {
return (this.name in getElementsCollection(this._res, 'inline_level'));
},
/**
* Проверяет, является ли текущий элемент блочным
* @return {Boolean}
*/
isBlock: function() {
return (this.name in getElementsCollection(this._res, 'block_level'));
},
/**
* This function tests if current tags' content contains xHTML tags.
* This function is mostly used for output formatting
*/
hasTagsInContent: function() {
return this.getContent() && re_tag.test(this.getContent());
},
/**
* Проверяет, есть ли блочные потомки у текущего тэга.
* Используется для форматирования
* @return {Boolean}
*/
hasBlockChildren: function() {
if (this.hasTagsInContent() && this.isBlock()) {
return true;
}
for (var i = 0; i < this.children.length; i++) {
if (this.children[i].isBlock())
return true;
}
return false;
},
/**
* Set textual content for tag
* @param {String} str Tag's content
*/
setContent: function(str) {
this._content = str;
},
/**
* Returns tag's textual content
* @return {String}
*/
getContent: function() {
return this._content;
},
/**
* Search for deepest and latest child of current element
* @return {Tag|null} Returns null if there's no children
*/
findDeepestChild: function() {
if (!this.children.length)
return null;
var deepest_child = this;
while (true) {
deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
if (!deepest_child.children.length)
break;
}
return deepest_child;
},
/**
* Transforms and formats tag into string using profile
* @param {String} profile Profile name
* @return {String}
* TODO Function is too large, need refactoring
*/
toString: function(profile_name) {
var result = [],
profile = (profile_name in profiles) ? profiles[profile_name] : profiles['plain'],
attrs = '',
content = '',
start_tag = '',
end_tag = '',
cursor = profile.place_cursor ? '|' : '',
self_closing = '',
attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
attr_name,
is_empty = (this.isEmpty() && !this.children.length);
if (profile.self_closing_tag == 'xhtml')
self_closing = ' /';
else if (profile.self_closing_tag === true)
self_closing = '/';
function allowNewline(tag) {
return (profile.tag_nl === true || (profile.tag_nl == 'decide' && tag.isBlock()))
}
// make attribute string
for (var i = 0; i < this.attributes.length; i++) {
var a = this.attributes[i];
attr_name = (profile.attr_case == 'upper') ? a.name.toUpperCase() : a.name.toLowerCase();
attrs += ' ' + attr_name + '=' + attr_quote + (a.value || cursor) + attr_quote;
}
var deepest_child = this.findDeepestChild();
// output children
if (!is_empty) {
if (deepest_child && this.repeat_by_lines)
deepest_child.setContent(content_placeholder);
for (var j = 0; j < this.children.length; j++) {
//
content += this.children[j].toString(profile_name);
if (
(j != this.children.length - 1) &&
( allowNewline(this.children[j]) || allowNewline(this.children[j + 1]) )
)
content += getNewline();
}
}
// define opening and closing tags
if (this.name) {
var tag_name = (profile.tag_case == 'upper') ? this.name.toUpperCase() : this.name.toLowerCase();
if (is_empty) {
start_tag = '<' + tag_name + attrs + self_closing + '>';
} else {
start_tag = '<' + tag_name + attrs + '>';
end_tag = '' + tag_name + '>';
}
}
// formatting output
if (profile.tag_nl !== false) {
if (
this.name &&
(
profile.tag_nl === true ||
this.hasBlockChildren()
)
) {
if (end_tag) { // non-empty tag: add indentation
start_tag += getNewline() + zen_settings.variables.indentation;
end_tag = getNewline() + end_tag;
} else { // empty tag
}
}
if (this.name) {
if (content)
content = padString(content, profile.indent ? 1 : 0);
else if (!is_empty)
start_tag += cursor;
}
}
// repeat tag by lines count
var cur_content = '';
if (this.repeat_by_lines) {
var lines = splitByLines( trim(this.getContent()) , true);
for (var j = 0; j < lines.length; j++) {
cur_content = deepest_child ? '' : content_placeholder;
if (content && !deepest_child)
cur_content += getNewline();
var elem_str = start_tag.replace(/\$/g, j + 1) + cur_content + content + end_tag;
result.push(elem_str.replace(content_placeholder, trim(lines[j])));
}
}
// repeat tag output
if (!result.length) {
if (this.getContent()) {
var pad = (profile.tag_nl === true || (this.hasTagsInContent() && this.isBlock())) ? 1 : 0;
content = padString(this.getContent(), pad) + content;
}
for (var i = 0; i < this.count; i++)
result.push(start_tag.replace(/\$/g, i + 1) + content + end_tag);
}
var glue = '';
if (allowNewline(this))
glue = getNewline();
return result.join(glue);
}
};
// TODO inherit from Tag
function Snippet(name, count, type) {
/** @type {String} */
this.name = name;
this.count = count || 1;
this.children = [];
this._content = '';
this.repeat_by_lines = false;
this.attributes = {'id': '|', 'class': '|'};
this.value = getSnippet(type, name);
}
Snippet.prototype = {
/**
* Добавляет нового потомка
* @param {Tag} tag
*/
addChild: function(tag) {
this.children.push(tag);
},
addAttribute: function(name, value){
this.attributes[name] = value;
},
isBlock: function() {
return true;
},
/**
* Set textual content for snippet
* @param {String} str Tag's content
*/
setContent: function(str) {
this._content = str;
},
/**
* Returns snippet's textual content
* @return {String}
*/
getContent: function() {
return this._content;
},
/**
* Search for deepest and latest child of current element
* @return {Tag|null} Returns null if there's no children
*/
findDeepestChild: function() {
if (!this.children.length)
return null;
var deepest_child = this;
while (true) {
deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
if (!deepest_child.children.length)
break;
}
return deepest_child;
},
toString: function(profile_name) {
var content = '',
profile = (profile_name in profiles) ? profiles[profile_name] : profiles['plain'],
result = [],
data = this.value,
begin = '',
end = '',
child_padding = '',
child_token = '${child}';
if (data) {
if (profile.tag_nl !== false) {
var nl = getNewline();
data = data.replace(/\n/g, nl);
// figuring out indentation for children
var lines = data.split(nl), m;
for (var j = 0; j < lines.length; j++) {
if (lines[j].indexOf(child_token) != -1) {
child_padding = (m = lines[j].match(/(^\s+)/)) ? m[1] : '';
break;
}
}
}
var parts = data.split(child_token);
begin = parts[0] || '';
end = parts[1] || '';
}
for (var i = 0; i < this.children.length; i++) {
content += this.children[i].toString(profile_name);
if (
i != this.children.length - 1 &&
(
profile.tag_nl === true ||
(profile.tag_nl == 'decide' && this.children[i].isBlock())
)
)
content += getNewline();
}
if (child_padding)
content = padString(content, child_padding);
// substitute attributes
begin = replaceVariables(begin, this.attributes);
end = replaceVariables(end, this.attributes);
if (this.getContent()) {
content = padString(this.getContent(), 1) + content;
}
// выводим тэг нужное количество раз
for (var i = 0; i < this.count; i++)
result.push(begin + content + end);
// result.push(begin.replace(/\$(?!\{)/g, i + 1) + content + end);
return result.join((profile.tag_nl !== false) ? getNewline() : '');
}
}
/**
* Returns abbreviation value from data set
* @param {String} type Resource type (html, css, ...)
* @param {String} abbr Abbreviation name
* @return {Object|null}
*/
function getAbbreviation(type, abbr) {
return getSettingsResource(type, abbr, 'abbreviations');
}
/**
* Returns snippet value from data set
* @param {String} type Resource type (html, css, ...)
* @param {String} snippet_name Snippet name
* @return {Object|null}
*/
function getSnippet(type, snippet_name) {
return getSettingsResource(type, snippet_name, 'snippets');
}
/**
* Returns resurce value from data set with respect of inheritance
* @param {String} type Resource type (html, css, ...)
* @param {String} abbr Abbreviation name
* @param {String} res_name Resource name ('snippets' or 'abbreviation')
* @return {Object|null}
*/
function getSettingsResource(type, abbr, res_name) {
var resource = zen_settings[type];
if (resource) {
if (res_name in resource && abbr in resource[res_name])
return resource[res_name][abbr];
else if ('extends' in resource) {
// find abbreviation in ancestors
for (var i = 0; i < resource['extends'].length; i++) {
var type = resource['extends'][i];
if (
zen_settings[type] &&
zen_settings[type][res_name] &&
zen_settings[type][res_name][abbr]
)
return zen_settings[type][res_name][abbr];
}
}
}
return null;
}
// create default profiles
setupProfile('xhtml');
setupProfile('html', {self_closing_tag: false});
setupProfile('xml', {self_closing_tag: true, tag_nl: true});
setupProfile('plain', {tag_nl: false, indent: false, place_cursor: false});
return {
expandAbbreviation: function(abbr, type, profile) {
var tree = this.parseIntoTree(abbr, type || 'html');
return replaceVariables(tree ? tree.toString(profile) : '');
},
/**
* Extracts abbreviations from text stream, starting from the end
* @param {String} str
* @return {String} Abbreviation or empty string
*/
extractAbbreviation: function(str) {
var cur_offset = str.length,
start_index = -1;
while (true) {
cur_offset--;
if (cur_offset < 0) {
// дошли до начала строки
start_index = 0;
break;
}
var ch = str.charAt(cur_offset);
if (!isAllowedChar(ch) || (ch == '>' && isEndsWithTag(str.substring(0, cur_offset + 1)))) {
start_index = cur_offset + 1;
break;
}
}
if (start_index != -1)
// что-то нашли, возвращаем аббревиатуру
return str.substring(start_index);
else
return '';
},
/**
* Parses abbreviation into a node set
* @param {String} abbr Abbreviation
* @param {String} type Document type (xsl, html, etc.)
* @return {Tag}
*/
parseIntoTree: function(abbr, type) {
type = type || 'html';
var root = new Tag('', 1, type),
parent = root,
last = null,
multiply_elem = null,
res = zen_settings[type],
re = /([\+>])?([a-z@\!][a-z0-9:\-]*)(#[\w\-\$]+)?((?:\.[\w\-\$]+)*)(\*(\d*))?(\+$)?/ig;
if (!abbr)
return null;
// replace expandos
abbr = abbr.replace(/([a-z][\w\:\-]*)\+$/i, function(str){
var a = getAbbreviation(type, str);
return a ? a.value : str;
});
abbr = abbr.replace(re, function(str, operator, tag_name, id, class_name, has_multiplier, multiplier, has_expando){
var multiply_by_lines = (has_multiplier && !multiplier);
multiplier = multiplier ? parseInt(multiplier) : 1;
if (has_expando)
tag_name += '+';
var current = isShippet(tag_name, type) ? new Snippet(tag_name, multiplier, type) : new Tag(tag_name, multiplier, type);
if (id)
current.addAttribute('id', id.substr(1));
if (class_name)
current.addAttribute('class', class_name.substr(1).replace(/\./g, ' '));
// dive into tree
if (operator == '>' && last)
parent = last;
parent.addChild(current);
last = current;
if (multiply_by_lines)
multiply_elem = current;
return '';
});
root.last = last;
root.multiply_elem = multiply_elem;
// empty 'abbr' string means that abbreviation was successfully expanded,
// if not — abbreviation wasn't valid
return (!abbr) ? root : null;
},
/**
* Отбивает текст отступами
* @param {String} text Текст, который нужно отбить
* @param {String|Number} pad Количество отступов или сам отступ
* @return {String}
*/
padString: padString,
setupProfile: setupProfile,
getNewline: function(){
return newline;
},
setNewline: function(str) {
newline = str;
},
/**
* Returns range for matched tag pair inside document
* @requires HTMLParser
* @param {String} html Full xHTML document
* @param {Number} cursor_pos Cursor position inside document
* @return {Object} Pair of indicies (start
and end
).
* Returns 'null' if match wasn't found
*/
getPairRange: function(html, cursor_pos) {
var tags = {},
ranges = [],
result = null;
function inRange(start, end) {
return cursor_pos > start && cursor_pos < end;
}
var handler = {
start: function(name, attrs, unary, ix_start, ix_end) {
if (unary && inRange(ix_start, ix_end)) {
// this is the exact range for cursor position, stop searching
result = {start: ix_start, end: ix_end};
this.stop = true;
} else {
if (!tags.hasOwnProperty(name))
tags[name] = [];
tags[name].push(ix_start);
}
},
end: function(name, ix_start, ix_end) {
if (tags.hasOwnProperty(name)) {
var start = tags[name].pop();
if (inRange(start, ix_end))
ranges.push({start: start, end: ix_end});
}
},
comment: function(data, ix_start, ix_end) {
if (inRange(ix_start, ix_end)) {
// this is the exact range for cursor position, stop searching
result = {start: ix_start, end: ix_end};
this.stop = true;
}
}
};
// scan document
try {
HTMLParser(html, handler);
} catch(e) {}
if (!result && ranges.length) {
// because we have overlaped ranges only, we have to sort array by
// length: the shorter range length, the most probable match
result = ranges.sort(function(a, b){
return (a.end - a.start) - (b.end - b.start);
})[0];
}
return result;
},
/**
* Wraps passed text with abbreviation. Text will be placed inside last
* expanded element
* @param {String} abbr Abbreviation
* @param {String} text Text to wrap
* @param {String} [type] Document type (html, xml, etc.). Default is 'html'
* @param {String} [profile] Output profile's name. Default is 'plain'
* @return {String}
*/
wrapWithAbbreviation: function(abbr, text, type, profile) {
var tree = this.parseIntoTree(abbr, type || 'html');
if (tree) {
var repeat_elem = tree.multiply_elem || tree.last;
repeat_elem.setContent(text);
repeat_elem.repeat_by_lines = !!tree.multiply_elem;
return tree.toString(profile);
} else {
return null;
}
},
splitByLines: splitByLines,
/**
* Check if cursor is placed inside xHTML tag
* @param {String} html Contents of the document
* @param {Number} cursor_pos Current caret position inside tag
* @return {Boolean}
*/
isInsideTag: function(html, cursor_pos) {
var re_tag = /^<\/?\w[\w\:\-]*.*?>/;
// search left to find opening brace
var pos = cursor_pos;
while (pos > -1) {
if (html.charAt(pos) == '<')
break;
pos--;
}
if (pos != -1) {
var m = re_tag.exec(html.substring(pos));
if (m && cursor_pos > pos && cursor_pos < pos + m[0].length)
return true;
}
return false;
},
settings_parser: (function(){
/**
* Unified object for parsed data
*/
function entry(type, key, value) {
return {
type: type,
key: key,
value: value
};
}
/** Regular expression for XML tag matching */
var re_tag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/,
re_attrs = /([\w\-]+)\s*=\s*(['"])(.*?)\2/g;
/**
* Make expando from string
* @param {String} key
* @param {String} value
* @return {Object}
*/
function makeExpando(key, value) {
return entry(TYPE_EXPANDO, key, value);
}
/**
* Make abbreviation from string
* @param {String} key Abbreviation key
* @param {String} tag_name Expanded element's tag name
* @param {String} attrs Expanded element's attributes
* @param {Boolean} is_empty Is expanded element empty or not
* @return {Object}
*/
function makeAbbreviation(key, tag_name, attrs, is_empty) {
var result = {
name: tag_name,
is_empty: Boolean(is_empty)
};
if (attrs) {
var m;
result.attributes = [];
while (m = re_attrs.exec(attrs)) {
result.attributes.push({
name: m[1],
value: m[3]
});
}
}
return entry(TYPE_ABBREVIATION, key, result);
}
/**
* Parses all abbreviations inside object
* @param {Object} obj
*/
function parseAbbreviations(obj) {
for (var key in obj) {
var value = obj[key], m;
key = trim(key);
if (key.substr(-1) == '+') {
// this is expando, leave 'value' as is
obj[key] = makeExpando(key, value);
} else if (m = re_tag.exec(value)) {
obj[key] = makeAbbreviation(key, m[1], m[2], m[4] == '/');
} else {
// assume it's reference to another abbreviation
obj[key] = entry(TYPE_REFERENCE, key, value);
}
}
}
return {
/**
* Parse user's settings
* @param {Object} settings
*/
parse: function(settings) {
for (var p in settings) {
if (p == 'abbreviations')
parseAbbreviations(settings[p]);
else if (p == 'extends') {
var ar = settings[p].split(',');
for (var i = 0; i < ar.length; i++)
ar[i] = trim(ar[i]);
settings[p] = ar;
}
else if (typeof(settings[p]) == 'object')
arguments.callee(settings[p]);
}
},
extend: function(parent, child) {
for (var p in child) {
if (typeof(child[p]) == 'object' && parent.hasOwnProperty(p))
arguments.callee(parent[p], child[p]);
else
parent[p] = child[p];
}
},
/**
* Create hash maps on certain string properties
* @param {Object} obj
*/
createMaps: function(obj) {
for (var p in obj) {
if (p == 'element_types') {
for (var k in obj[p])
obj[p][k] = stringToHash(obj[p][k]);
} else if (typeof(obj[p]) == 'object') {
arguments.callee(obj[p]);
}
}
},
TYPE_ABBREVIATION: TYPE_ABBREVIATION,
TYPE_EXPANDO: TYPE_EXPANDO,
/** Reference to another abbreviation or tag */
TYPE_REFERENCE: TYPE_REFERENCE
}
})()
}
})();
if ('zen_settings' in this || zen_settings) {
// first we need to expand some strings into hashes
zen_coding.settings_parser.createMaps(zen_settings);
if ('my_zen_settings' in this) {
// we need to extend default settings with user's
zen_coding.settings_parser.createMaps(my_zen_settings);
zen_coding.settings_parser.extend(zen_settings, my_zen_settings);
}
// now we need to parse final set of settings
zen_coding.settings_parser.parse(zen_settings);
}/**
* High-level editor interface which communicates with other editor (like
* TinyMCE, CKEditor, etc.) or browser.
* Before using any of editor's methods you should initialize it with
* editor.setTarget(elem)
method and pass reference to
* <textarea> element.
* @example
* var textarea = document.getElemenetsByTagName('textarea')[0];
* editor.setTarget(textarea);
* //now you are ready to use editor object
* editor.getSelectionRange()
*
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
* @include "../../aptana/lib/zen_coding.js"
*/
var zen_editor = (function(){
/** @param {Element} Source element */
var target = null,
/** Textual placeholder that identifies cursor position in pasted text */
caret_placeholder = '|';
// different browser uses different newlines, so we have to figure out
// native browser newline and sanitize incoming text with them
var tx = document.createElement('textarea');
tx.value = '\n';
zen_coding.setNewline(tx.value);
tx = null;
/**
* Returns content of current target element
*/
function getContent() {
return target.value || '';
}
/**
* Returns selection indexes from element
*/
function getSelectionRange() {
if ('selectionStart' in target) { // W3C's DOM
var length = target.selectionEnd - target.selectionStart;
return {
start: target.selectionStart,
end: target.selectionEnd
};
} else if (document.selection) { // IE
target.focus();
var range = document.selection.createRange();
if (range === null) {
return {
start: 0,
end: getContent().length
};
}
var re = target.createTextRange();
var rc = re.duplicate();
re.moveToBookmark(range.getBookmark());
rc.setEndPoint('EndToStart', re);
return {
start: rc.text.length,
end: rc.text.length + range.text.length
};
} else {
return null;
}
}
/**
* Creates text selection on target element
* @param {Number} start
* @param {Number} end
*/
function createSelection(start, end) {
// W3C's DOM
if (typeof(end) == 'undefined')
end = start;
if ('setSelectionRange' in target) {
target.setSelectionRange(start, end);
} else if ('createTextRange' in target) {
var t = target.createTextRange();
t.collapse(true);
var delta = zen_coding.splitByLines(getContent().substring(0, start)).length - 1;
// IE has an issue with handling newlines while creating selection,
// so we need to adjust start and end indexes
end -= delta + zen_coding.splitByLines(getContent().substring(start, end)).length - 1;
start -= delta;
t.moveStart('character', start);
t.moveEnd('character', end - start);
t.select();
}
}
/**
* Find start and end index of text line for from
index
* @param {String} text
* @param {Number} from
*/
function findNewlineBounds(text, from) {
var len = text.length,
start = 0,
end = len - 1;
// search left
for (var i = from - 1; i > 0; i--) {
var ch = text.charAt(i);
if (ch == '\n' || ch == '\r') {
start = i + 1;
break;
}
}
// search right
for (var j = from; j < len; j++) {
var ch = text.charAt(j);
if (ch == '\n' || ch == '\r') {
end = j;
break;
}
}
return {start: start, end: end};
}
/**
* Returns current caret position
*/
function getCaretPos() {
var selection = getSelectionRange();
return selection ? selection.start : null;
}
/**
* Returns whitrespace padding of string
* @param {String} str String line
* @return {String}
*/
function getStringPadding(str) {
return (str.match(/^(\s+)/) || [''])[0];
}
return {
setTarget: function(elem) {
target = elem;
},
getSelectionRange: getSelectionRange,
createSelection: createSelection,
/**
* Returns current line's start and end indexes
*/
getCurrentLineRange: function() {
var caret_pos = getCaretPos(),
content = getContent();
if (caret_pos === null) return null;
return findNewlineBounds(content, caret_pos);
},
/**
* Returns current caret position
* @return {Number}
*/
getCaretPos: getCaretPos,
/**
* Returns content of current line
* @return {String}
*/
getCurrentLine: function() {
var range = this.getCurrentLineRange();
return range.start < range.end ? this.getContent().substring(range.start, range.end) : '';
},
/**
* Replace editor's content or it's part (from start
to
* end
index). If value
contains
* caret_placeholder
, the editor will put caret into
* this position. If you skip start
and end
* arguments, the whole target's content will be replaced with
* value
.
*
* If you pass start
argument only,
* the value
will be placed at start
string
* index of current content.
*
* If you pass start
and end
arguments,
* the corresponding substring of current target's content will be
* replaced with value
.
* @param {String} value Content you want to paste
* @param {Number} [start] Start index of editor's content
* @param {Number} [end] End index of editor's content
*/
replaceContent: function(value, start, end) {
var content = getContent(),
caret_pos = getCaretPos(),
has_start = typeof(start) !== 'undefined',
has_end = typeof(end) !== 'undefined';
// indent new value
value = zen_coding.padString(value, getStringPadding(this.getCurrentLine()));
// find new caret position
var new_pos = value.indexOf(caret_placeholder);
if (new_pos != -1) {
caret_pos = (start || 0) + new_pos;
value = value.split(caret_placeholder).join('');
} else {
caret_pos += value.length;
}
try {
if (has_start && has_end) {
content = content.substring(0, start) + value + content.substring(end);
} else if (has_start) {
content = content.substring(0, start) + value + content.substring(start);
}
target.value = content;
createSelection(caret_pos, caret_pos);
} catch(e){}
},
/**
* Returns editor's content
* @return {String}
*/
getContent: getContent
}
})();
/**
* Middleware layer that communicates between editor and Zen Coding.
* This layer describes all available Zen Coding actions, like
* "Expand Abbreviation".
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*
* @include "editor.js"
* @include "../../aptana/lib/html_matcher.js"
* @include "../../aptana/lib/zen_coding.js"
*/
/**
* Search for abbreviation in editor from current caret position
* @param {zen_editor} editor Editor instance
* @return {String|null}
*/
function findAbbreviation(editor) {
var range = editor.getSelectionRange();
if (range.start != range.end) {
// abbreviation is selected by user
return editor.getContent().substring(range.start, range.end);
}
// search for new abbreviation from current caret position
var cur_line = editor.getCurrentLineRange();
return zen_coding.extractAbbreviation(editor.getContent().substring(cur_line.start, range.start));
}
/**
* Find from current caret position and expand abbreviation in editor
* @param {zen_editor} editor Editor instance
* @param {String} type Syntax type (html, css, etc.)
* @param {String} profile_name Output profile name (html, xml, xhtml)
* @return {Boolean} Returns true
if abbreviation was expanded
* successfully
*/
function expandAbbreviation(editor, type, profile_name) {
profile_name = profile_name || 'xhtml';
var caret_pos = editor.getSelectionRange().end,
abbr,
content = '';
if ( (abbr = findAbbreviation(editor)) ) {
content = zen_coding.expandAbbreviation(abbr, type, profile_name);
if (content) {
editor.replaceContent(content, caret_pos - abbr.length, caret_pos);
return true;
}
}
return false;
}
/**
* A special version of expandAbbreviation
function: if it can't
* find abbreviation, it will place Tab character at caret position
* @param {zen_editor} editor Editor instance
* @param {String} type Syntax type (html, css, etc.)
* @param {String} profile_name Output profile name (html, xml, xhtml)
*/
function expandAbbreviationWithTab(editor, type, profile_name) {
if (!expandAbbreviation(editor, type, profile_name))
editor.replaceContent('\t', editor.getCaretPos());
}
/**
* Find and select HTML tag pair
* @param {zen_editor} editor Editor instance
* @param {String} [direction] Direction of pair matching: 'in' or 'out'.
* Default is 'out'
*/
function matchPair(editor, direction) {
direction = (direction || 'out').toLowerCase();
var range = editor.getSelectionRange(),
cursor = range.end,
range_start = range.start,
range_end = range.end,
// content = zen_coding.splitByLines(editor.getContent()).join('\n'),
content = editor.getContent(),
range = null,
_r,
old_open_tag = HTMLPairMatcher.last_match['opening_tag'],
old_close_tag = HTMLPairMatcher.last_match['closing_tag'];
if (direction == 'in' && old_open_tag && range_start != range_end) {
// user has previously selected tag and wants to move inward
if (!old_close_tag) {
// unary tag was selected, can't move inward
return false;
} else if (old_open_tag.start == range_start) {
if (content[old_open_tag.end] == '<') {
// test if the first inward tag matches the entire parent tag's content
_r = HTMLPairMatcher.find(content, old_open_tag.end + 1);
if (_r[0] == old_open_tag.end && _r[1] == old_close_tag.start) {
range = HTMLPairMatcher(content, old_open_tag.end + 1);
} else {
range = [old_open_tag.end, old_close_tag.start];
}
} else {
range = [old_open_tag.end, old_close_tag.start];
}
} else {
var new_cursor = content.substring(0, old_close_tag.start).indexOf('<', old_open_tag.end);
var search_pos = new_cursor != -1 ? new_cursor + 1 : old_open_tag.end;
range = HTMLPairMatcher(content, search_pos);
}
} else {
range = HTMLPairMatcher(content, cursor);
}
if (range !== null && range[0] != -1) {
// alert(range[0] + ', '+ range[1]);
editor.createSelection(range[0], range[1]);
return true;
} else {
return false;
}
}
/**
* Wraps content with abbreviation
* @param {zen_editor} Editor instance
* @param {String} type Syntax type (html, css, etc.)
* @param {String} profile_name Output profile name (html, xml, xhtml)
*/
function wrapWithAbbreviation(editor, abbr, type, profile_name) {
profile_name = profile_name || 'xhtml';
var range = editor.getSelectionRange(),
start_offset = range.start,
end_offset = range.end,
content = editor.getContent();
if (!abbr)
return null;
if (start_offset == end_offset) {
// no selection, find tag pair
range = HTMLPairMatcher(content, start_offset);
if (!range || range[0] == -1) // nothing to wrap
return null;
start_offset = range[0];
end_offset = range[1];
// narrow down selection until first non-space character
var re_space = /\s|\n|\r/;
function isSpace(ch) {
return re_space.test(ch);
}
while (start_offset < end_offset) {
if (!isSpace(content.charAt(start_offset)))
break;
start_offset++;
}
while (end_offset > start_offset) {
end_offset--;
if (!isSpace(content.charAt(end_offset))) {
end_offset++;
break;
}
}
}
var new_content = content.substring(start_offset, end_offset),
result = zen_coding.wrapWithAbbreviation(abbr, unindent(editor, new_content), type, profile_name);
if (result) {
editor.createSelection(end_offset);
editor.replaceContent(result, start_offset, end_offset);
}
}
/**
* Unindent content, thus preparing text for tag wrapping
* @param {zen_editor} Editor instance
* @param {String} text
* @return {String}
*/
function unindent(editor, text) {
var pad = getCurrentLinePadding(editor);
var lines = zen_coding.splitByLines(text);
for (var i = 0; i < lines.length; i++) {
if (lines[i].search(pad) == 0)
lines[i] = lines[i].substr(pad.length);
}
return lines.join(zen_coding.getNewline());
}
/**
* Returns padding of current editor's line
* @param {zen_editor} Editor instance
* @return {String}
*/
function getCurrentLinePadding(editor) {
return (editor.getCurrentLine().match(/^(\s+)/) || [''])[0];
}
/**
* Search for new caret insertion point
* @param {zen_editor} editor Editor instance
* @param {Number} Search increment: -1 — search left, 1 — search right
* @param {Number} 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 cur_point = editor.getCaretPos() + offset,
content = editor.getContent(),
max_len = content.length,
next_point = -1,
re_empty_line = /^\s+$/;
function ch(ix) {
return content.charAt(ix);
}
function getLine(ix) {
var start = ix;
while (start >= 0) {
var c = ch(start);
if (c == '\n' || c == '\r')
break;
start--;
}
return content.substring(start, ix);
}
while (cur_point < max_len && cur_point > 0) {
cur_point += inc;
var cur_char = ch(cur_point),
next_char = ch(cur_point + 1),
prev_char = ch(cur_point - 1);
switch (cur_char) {
case '"':
case '\'':
if (next_char == cur_char && prev_char == '=') {
// empty attribute
next_point = cur_point + 1;
}
break;
case '>':
if (next_char == '<') {
// between tags
next_point = cur_point + 1;
}
break;
case '\n':
case '\r':
// empty line
if (re_empty_line.test(getLine(cur_point - 1))) {
next_point = cur_point;
}
break;
}
if (next_point != -1)
break;
}
return next_point;
}
/**
* Move caret to previous edit point
* @param {zen_editor} editor Editor instance
*/
function prevEditPoint(editor) {
var cur_pos = editor.getCaretPos(),
new_point = findNewEditPoint(editor, -1);
if (new_point == cur_pos)
// we're still in the same point, try searching from the other place
new_point = findNewEditPoint(editor, -1, -2);
if (new_point != -1)
editor.createSelection(new_point);
}
/**
* Move caret to next edit point
* @param {zen_editor} editor Editor instance
*/
function nextEditPoint(editor) {
var new_point = findNewEditPoint(editor, 1);
if (new_point != -1)
editor.createSelection(new_point);
}
/**
* Inserts newline character with proper indentation
* @param {zen_editor} editor Editor instance
* @param {String} mode Syntax mode (only 'html' is implemented)
*/
function insertFormattedNewline(editor, mode) {
mode = mode || 'html';
var pad = getCurrentLinePadding(editor),
caret_pos = editor.getCaretPos();
function insert_nl() {
editor.replaceContent('\n', caret_pos);
}
switch (mode) {
case 'html':
// let's see if we're breaking newly created tag
var pair = HTMLPairMatcher.getTags(editor.getContent(), editor.getCaretPos());
if (pair[0] && pair[1] && pair[0].type == 'tag' && pair[0].end == caret_pos && pair[1].start == caret_pos) {
editor.replaceContent('\n\t|\n', caret_pos);
} else {
insert_nl();
}
break;
default:
insert_nl();
}
}
/**
* Select line under cursor
* @param {zen_editor} editor Editor instance
*/
function selectLine(editor) {
var range = editor.getCurrentLineRange();
editor.createSelection(range.start, range.end);
}/**
* http://www.openjs.com/scripts/events/keyboard_shortcuts/
* Version : 2.01.B
* By Binny V A
* License : BSD
*/
shortcut = {
'all_shortcuts':{},//All the shortcuts are stored in this array
'add': function(shortcut_combination,callback,opt) {
var is_opera = !!window.opera,
is_mac = /mac\s+os/i.test(navigator.userAgent);
//Provide a set of default options
var default_options = {
'type':is_opera ? 'keypress' : 'keydown',
'propagate':false,
'disable_in_input':false,
'target':document,
'keycode':false
}
if(!opt) opt = default_options;
else {
for(var dfo in default_options) {
if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];
}
}
var ele = opt.target;
if(typeof opt.target == 'string') ele = document.getElementById(opt.target);
var ths = this;
shortcut_combination = shortcut_combination.toLowerCase();
//The function to be called at keypress
var func = function(e) {
e = e || window.event;
var code;
if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields
var element;
if(e.target) element=e.target;
else if(e.srcElement) element=e.srcElement;
if(element.nodeType==3) element=element.parentNode;
if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return;
}
//Find Which key is pressed
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var character = String.fromCharCode(code).toLowerCase();
if(code == 188) character=","; //If the user presses , when the type is onkeydown
if(code == 190) character="."; //If the user presses , when the type is onkeydown
var keys = shortcut_combination.split("+");
//Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
//Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`":"~",
"1":"!",
"2":"@",
"3":"#",
"4":"$",
"5":"%",
"6":"^",
"7":"&",
"8":"*",
"9":"(",
"0":")",
"-":"_",
"=":"+",
";":":",
"'":"\"",
",":"<",
".":">",
"/":"?",
"\\":"|"
}
//Special Keys - and their codes
var special_keys = {
'esc':27,
'escape':27,
'tab':9,
'space':32,
'return':13,
'enter':13,
'backspace':8,
'scrolllock':145,
'scroll_lock':145,
'scroll':145,
'capslock':20,
'caps_lock':20,
'caps':20,
'numlock':144,
'num_lock':144,
'num':144,
'pause':19,
'break':19,
'insert':45,
'home':36,
'delete':46,
'end':35,
'pageup':33,
'page_up':33,
'pu':33,
'pagedown':34,
'page_down':34,
'pd':34,
'plus': 187,
'minus': 189,
'left':37,
'up':38,
'right':39,
'down':40,
'f1':112,
'f2':113,
'f3':114,
'f4':115,
'f5':116,
'f6':117,
'f7':118,
'f8':119,
'f9':120,
'f10':121,
'f11':122,
'f12':123
}
var modifiers = {
shift: { wanted:false, pressed:false},
ctrl : { wanted:false, pressed:false},
alt : { wanted:false, pressed:false},
meta : { wanted:false, pressed:false} //Meta is Mac specific
};
if(e.ctrlKey) modifiers.ctrl.pressed = true;
if(e.shiftKey) modifiers.shift.pressed = true;
if(e.altKey) modifiers.alt.pressed = true;
if(e.metaKey) modifiers.meta.pressed = true;
var k;
for(var i=0; k=keys[i], idefault_options
*/
setup: function(opt) {
options = copyOptions(opt);
},
/**
* Returns option value
*/
getOption: function(name) {
return options[name];
},
/**
* Returns array of binded actions and their keystrokes
* @return {Array}
*/
getShortcuts: function() {
return formatShortcut(is_mac ? mac_char_map : pc_char_map, is_mac ? '' : '+');
},
/**
* Show info window about Zen Coding
*/
showInfo: function() {
var message = 'All textareas on this page are powered by Zen Coding project: ' +
'a set of tools for fast HTML coding.\n\n' +
'Available shortcuts:\n';
var sh = this.getShortcuts(),
actions = [];
for (var i = 0; i < sh.length; i++) {
actions.push(sh[i].keystroke + ' — ' + sh[i].action_name)
}
message += actions.join('\n') + '\n\n';
message += 'More info on http://code.google.com/p/zen-coding/';
alert(message);
}
}
})();
})();