(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' + ' \n' + ' \n' + '\n' + '\n\t${child}|\n\n' + '', 'html:4s': '\n' + '\n' + '\n' + ' \n' + ' \n' + '\n' + '\n\t${child}|\n\n' + '', 'html:xt': '\n' + '\n' + '\n' + ' \n' + ' \n' + '\n' + '\n\t${child}|\n\n' + '', 'html:xs': '\n' + '\n' + '\n' + ' \n' + ' \n' + '\n' + '\n\t${child}|\n\n' + '', 'html:xxs': '\n' + '\n' + '\n' + ' \n' + ' \n' + '\n' + '\n\t${child}|\n\n' + '', 'html:5': '\n' + '\n' + '\n' + ' \n' + ' \n' + '\n' + '\n\t${child}|\n\n' + '', 'more': '\n\n' }, 'abbreviations': { 'a': '', 'a:link': '', 'a:mail': '', 'abbr': '', 'acronym': '', 'base': '', 'bdo': '', 'bdo:r': '', 'bdo:l': '', 'link:css': '', 'link:print': '', 'link:favicon': '', 'link:touch': '', 'link:rss': '', 'link:atom': '', 'meta:utf': '', 'meta:win': '', 'meta:compat': '', 'style': '', 'script': '', 'script:src': '', 'img': '', 'iframe': '', 'embed': '', 'object': '', 'param': '', 'map': '', 'area': '', 'area:d': '', 'area:c': '', 'area:r': '', 'area:p': '', 'link': '', 'form': '
', 'form:get': '
', 'form:post': '
', 'label': '', 'input': '', 'input:hidden': '', 'input:h': '', 'input:text': '', 'input:t': '', 'input:search': '', 'input:email': '', 'input:url': '', 'input:password': '', 'input:p': '', 'input:datetime': '', 'input:date': '', 'input:datetime-local': '', 'input:month': '', 'input:week': '', 'input:time': '', 'input:number': '', 'input:color': '', 'input:checkbox': '', 'input:c': '', 'input:radio': '', 'input:r': '', 'input:range': '', 'input:file': '', 'input:f': '', 'input:submit': '', 'input:s': '', 'input:image': '', 'input:i': '', 'input:reset': '', 'input:button': '', 'input:b': '', 'select': '', 'option': '', 'textarea': '', 'menu:context': '', 'menu:c': '', 'menu:toolbar': '', 'menu:t': '', 'video': '', 'audio': '', 'html:xml': '', 'bq': '
', 'acr': '', 'fig': '
', 'ifr': '', 'emb': '', 'obj': '', 'src': '', 'cap': '', 'colg': '', 'fst': '
', 'btn': '', 'optg': '', 'opt': '', 'tarea': '', 'leg': '', 'sect': '
', 'art': '
', 'hdr': '
', 'ftr': '', 'adr': '
', 'dlg': '', 'str': '', 'prog': '', 'fset': '
', 'datag': '', 'datal': '', 'kg': '', 'out': '', 'det': '
', 'cmd': '', // expandos 'ol+': 'ol>li', 'ul+': 'ul>li', 'dl+': 'dl>dt+dd', 'map+': 'map>area', 'table+': 'table>tr>td', 'colgroup+': 'colgroup>col', 'colg+': 'colgroup>col', 'tr+': 'tr>td', 'select+': 'select>option', 'optgroup+': 'optgroup>option', 'optg+': 'optgroup>option' }, 'element_types': { 'empty': 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command', 'block_level': 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6', 'inline_level': 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var' } }, 'xsl': { 'extends': 'html', 'abbreviations': { 'tm': '', 'tmatch': 'tm', 'tn': '', 'tname': 'tn', 'xsl:when': '', 'wh': 'xsl:when', 'var': '|', 'vare': '', 'if': '', 'call': '', 'attr': '', 'wp': '', 'par': '', 'val': '', 'co': '', 'each': '', 'ap': '', //expandos 'choose+': 'xsl:choose>xsl:when+xsl:otherwise' } } };/** * @author Sergey Chikuyonok (serge.che@gmail.com) * @link http://chikuyonok.ru */ (function(){ // Regular Expressions for parsing tags and attributes var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, end_tag = /^<\/([\w\:\-]+)[^>]*>/, attr = /([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 4.01 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"); // Block Elements - HTML 4.01 var block = makeMap("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul"); // Inline Elements - HTML 4.01 var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); // Elements that you can, intentionally, leave open // (and which close themselves) var close_self = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); /** Last matched HTML pair */ var last_match = { opening_tag: null, // tag() or comment() object closing_tag: null, // tag() or comment() object start_ix: -1, end_ix: -1 }; function tag(match, ix) { var name = match[1].toLowerCase(); return { name: name, full_tag: match[0], start: ix, end: ix + match[0].length, unary: Boolean(match[3]) || (name in empty), type: 'tag', close_self: (name in close_self) }; } function comment(start, end) { return { start: start, end: end, type: 'comment' }; } function makeMap(str){ var obj = {}, items = str.split(","); for ( var i = 0; i < items.length; i++ ) obj[ items[i] ] = true; return obj; } /** * Makes selection ranges for matched tag pair * @param {tag} opening_tag * @param {tag} closing_tag * @param {Number} ix */ function makeRange(opening_tag, closing_tag, ix) { ix = ix || 0; var start_ix = -1, end_ix = -1; if (opening_tag && !closing_tag) { // unary element start_ix = opening_tag.start; end_ix = opening_tag.end; } else if (opening_tag && closing_tag) { // complete element if ( (opening_tag.start < ix && opening_tag.end > ix) || (closing_tag.start <= ix && closing_tag.end > ix) ) { start_ix = opening_tag.start; end_ix = closing_tag.end; } else { start_ix = opening_tag.end; end_ix = closing_tag.start; } } return [start_ix, end_ix]; } /** * Save matched tag for later use and return found indexes * @param {tag} opening_tag * @param {tag} closing_tag * @param {Number} ix * @return {Array} */ function saveMatch(opening_tag, closing_tag, ix) { ix = ix || 0; last_match.opening_tag = opening_tag; last_match.closing_tag = closing_tag; var range = makeRange(opening_tag, closing_tag, ix); last_match.start_ix = range[0]; last_match.end_ix = range[1]; return last_match.start_ix != -1 ? [last_match.start_ix, last_match.end_ix] : null; } /** * Search for matching tags in 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.
or
self_closing_tag: 'xhtml' }; var profiles = {}; /** * Проверяет, является ли символ допустимым в аббревиатуре * @param {String} ch * @return {Boolean} */ function isAllowedChar(ch) { var char_code = ch.charCodeAt(0), special_chars = '#.>+*:$-_!@'; return (char_code > 64 && char_code < 91) // uppercase letter || (char_code > 96 && char_code < 123) // lowercase letter || (char_code > 47 && char_code < 58) // number || special_chars.indexOf(ch) != -1; // special character } /** * Возвращает символ перевода строки, используемый в редакторе * @return {String} */ function getNewline() { return zen_coding.getNewline(); } /** * Split text into lines. Set 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 = ''; } } // 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], i 1) { //If it is a special key if(special_keys[k] == code) kp++; } else if(opt['keycode']) { if(opt['keycode'] == code) kp++; } else { //The special keys did not match if(character == k) kp++; else { if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase character = shift_nums[character]; if(character == k) kp++; } } } } if(kp == keys.length && modifiers.ctrl.pressed == modifiers.ctrl.wanted && modifiers.shift.pressed == modifiers.shift.wanted && modifiers.alt.pressed == modifiers.alt.wanted && modifiers.meta.pressed == modifiers.meta.wanted) { var result = callback(e); if(result !== true && !opt['propagate']) { //Stop the event //e.cancelBubble is supported by IE - this will kill the bubbling process. e.cancelBubble = true; e.returnValue = false; //e.stopPropagation works in Firefox. if (e.stopPropagation) { e.stopPropagation(); e.preventDefault(); } return false; } } } this.all_shortcuts[shortcut_combination] = { 'callback':func, 'target':ele, 'event': opt['type'] }; //Attach the function with the event if(ele.addEventListener) ele.addEventListener(opt['type'], func, false); else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func); else ele['on'+opt['type']] = func; }, //Remove the shortcut - just specify the shortcut and I will remove the binding 'remove':function(shortcut_combination) { shortcut_combination = shortcut_combination.toLowerCase(); var binding = this.all_shortcuts[shortcut_combination]; delete(this.all_shortcuts[shortcut_combination]) if(!binding) return; var type = binding['event']; var ele = binding['target']; var callback = binding['callback']; if(ele.detachEvent) ele.detachEvent('on'+type, callback); else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); else ele['on'+type] = false; } }/** * Editor manager that handles all incoming events and runs Zen Coding actions. * This manager is also used for setting up editor preferences * @author Sergey Chikuyonok (serge.che@gmail.com) * @link http://chikuyonok.ru * * @include "actions.js" * @include "editor.js" * @include "shortcut.js" */ zen_textarea = (function(){ // should be global var default_options = { profile: 'xhtml', syntax: 'html', use_tab: false, pretty_break: false }, mac_char_map = { 'ctrl': '⌃', 'control': '⌃', 'meta': '⌘', 'shift': '⇧', 'alt': '⌥', 'enter': '⏎', 'tab': '⇥', 'left': '←', 'right': '→' }, pc_char_map = { 'left': '←', 'right': '→' }, shortcuts = {}, is_mac = /mac\s+os/i.test(navigator.userAgent), /** Zen Coding parameter name/value regexp for getting options from element */ re_param = /\bzc\-(\w+)\-(\w+)/g; /** @type {default_options} */ var options = {}; function copyOptions(opt) { opt = opt || {}; var result = {}; for (var p in default_options) if (default_options.hasOwnProperty(p)) { result[p] = (p in opt) ? opt[p] : default_options[p]; } return result; } options = copyOptions(); /** * Makes first letter of string in uppercase * @param {String} str */ function capitalize(str) { return str.charAt().toUpperCase() + str.substring(1); } function humanize(str) { return capitalize(str.replace(/_(\w)/g, function(s, p){return ' ' + p.toUpperCase()})); } function formatShortcut(char_map, glue) { var result = []; if (typeof(glue) == 'undefined') glue = '+'; for (var p in shortcuts) if (shortcuts.hasOwnProperty(p)) { var keys = p.split('+'), ar = [], lp = p.toLowerCase(); if (lp == 'tab' || lp == 'enter') continue; for (var i = 0; i < keys.length; i++) { var key = keys[i].toLowerCase(); ar.push(key in char_map ? char_map[key] : capitalize(key)); } result.push({ 'keystroke': ar.join(glue), 'action_name': humanize(shortcuts[p]) }); } return result; } /** * Get Zen Coding options from element's class name * @param {Element} elem */ function getOptionsFromElement(elem) { var param_str = elem.className || '', m, result = copyOptions(options); while ( (m = re_param.exec(param_str)) ) { var key = m[1].toLowerCase(), value = m[2].toLowerCase(); if (value == 'true' || value == 'yes' || value == '1') value = true; else if (value == 'false' || value == 'no' || value == '0') value = false; result[key] = value; } return result; } /** * Returns normalized action name * @param {String} name Action name (like 'Expand Abbreviation') * @return Normalized name for coding (like 'expand_abbreviation') */ function normalizeActionName(name) { return name .replace(/(^\s+|\s+$)/g, '') // remove trailing spaces .replace(/\s+/g, '_') .toLowerCase(); } /** * Runs actions called by user * @param {String} name Normalized action name * @param {Event} evt Event object */ function runAction(name, evt) { /** @type {Element} */ var target_elem = evt.target || evt.srcElement, key_code = evt.keyCode || evt.which; if (target_elem && target_elem.nodeType == 1 && target_elem.nodeName == 'TEXTAREA') { zen_editor.setTarget(target_elem); var options = getOptionsFromElement(target_elem), syntax = options.syntax, profile_name = options.profile; switch (name) { case 'expand_abbreviation': if (key_code == 9) { if (options.use_tab) expandAbbreviationWithTab(zen_editor, syntax, profile_name); else // user pressed Tab key but it's forbidden in // Zen Coding: bubble up event return true; } else { expandAbbreviation(zen_editor, syntax, profile_name); } break; case 'match_pair_inward': case 'balance_tag_inward': matchPair(zen_editor, 'in'); break; case 'match_pair_outward': case 'balance_tag_outward': matchPair(zen_editor, 'out'); break; case 'wrap_with_abbreviation': var abbr = prompt('Enter abbreviation', 'div'); if (abbr) wrapWithAbbreviation(zen_editor, abbr, syntax, profile_name); break; case 'next_edit_point': nextEditPoint(zen_editor); break; case 'previous_edit_point': case 'prev_edit_point': prevEditPoint(zen_editor); break; case 'pretty_break': case 'format_line_break': if (key_code == 13) { if (options.pretty_break) insertFormattedNewline(zen_editor); else // user pressed Enter but it's forbidden in // Zen Coding: bubble up event return true; } else { insertFormattedNewline(zen_editor); } break; case 'select_line': selectLine(zen_editor); } } else { // allow event bubbling return true; } } /** * Bind shortcut to Zen Coding action * @param {String} keystroke * @param {String} action_name */ function addShortcut(keystroke, action_name) { action_name = normalizeActionName(action_name); shortcuts[keystroke.toLowerCase()] = action_name; shortcut.add(keystroke, function(evt){ return runAction(action_name, evt); }); } // add default shortcuts addShortcut('Meta+E', 'Expand Abbreviation'); addShortcut('Tab', 'Expand Abbreviation'); addShortcut('Meta+D', 'Balance Tag Outward'); addShortcut('Shift+Meta+D', 'Balance Tag inward'); addShortcut('Shift+Meta+A', 'Wrap with Abbreviation'); addShortcut('Ctrl+RIGHT', 'Next Edit Point'); addShortcut('Ctrl+LEFT', 'Previous Edit Point'); addShortcut('Meta+L', 'Select Line'); addShortcut('Enter', 'Format Line Break'); return { shortcut: addShortcut, /** * Removes shortcut binding * @param {String} keystroke */ unbindShortcut: function(keystroke) { keystroke = keystroke.toLowerCase(); if (keystroke in shortcuts) delete shortcuts[keystroke]; shortcut.remove(keystroke); }, /** * Setup editor. Pass object with values defined in * default_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); } } })(); })();