openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

closetag.js (7705B)


      1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
      2 // Distributed under an MIT license: http://codemirror.net/LICENSE
      3 
      4 /**
      5  * Tag-closer extension for CodeMirror.
      6  *
      7  * This extension adds an "autoCloseTags" option that can be set to
      8  * either true to get the default behavior, or an object to further
      9  * configure its behavior.
     10  *
     11  * These are supported options:
     12  *
     13  * `whenClosing` (default true)
     14  *   Whether to autoclose when the '/' of a closing tag is typed.
     15  * `whenOpening` (default true)
     16  *   Whether to autoclose the tag when the final '>' of an opening
     17  *   tag is typed.
     18  * `dontCloseTags` (default is empty tags for HTML, none for XML)
     19  *   An array of tag names that should not be autoclosed.
     20  * `indentTags` (default is block tags for HTML, none for XML)
     21  *   An array of tag names that should, when opened, cause a
     22  *   blank line to be added inside the tag, and the blank line and
     23  *   closing line to be indented.
     24  *
     25  * See demos/closetag.html for a usage example.
     26  */
     27 
     28 (function(mod) {
     29   if (typeof exports == "object" && typeof module == "object") // CommonJS
     30     mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
     31   else if (typeof define == "function" && define.amd) // AMD
     32     define(["../../lib/codemirror", "../fold/xml-fold"], mod);
     33   else // Plain browser env
     34     mod(CodeMirror);
     35 })(function(CodeMirror) {
     36   CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
     37     if (old != CodeMirror.Init && old)
     38       cm.removeKeyMap("autoCloseTags");
     39     if (!val) return;
     40     var map = {name: "autoCloseTags"};
     41     if (typeof val != "object" || val.whenClosing)
     42       map["'/'"] = function(cm) { return autoCloseSlash(cm); };
     43     if (typeof val != "object" || val.whenOpening)
     44       map["'>'"] = function(cm) { return autoCloseGT(cm); };
     45     cm.addKeyMap(map);
     46   });
     47 
     48   var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
     49                        "source", "track", "wbr"];
     50   var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
     51                     "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
     52 
     53   function autoCloseGT(cm) {
     54     if (cm.getOption("disableInput")) return CodeMirror.Pass;
     55     var ranges = cm.listSelections(), replacements = [];
     56     for (var i = 0; i < ranges.length; i++) {
     57       if (!ranges[i].empty()) return CodeMirror.Pass;
     58       var pos = ranges[i].head, tok = cm.getTokenAt(pos);
     59       var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
     60       if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
     61 
     62       var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
     63       var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
     64       var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
     65 
     66       var tagName = state.tagName;
     67       if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
     68       var lowerTagName = tagName.toLowerCase();
     69       // Don't process the '>' at the end of an end-tag or self-closing tag
     70       if (!tagName ||
     71           tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
     72           tok.type == "tag" && state.type == "closeTag" ||
     73           tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
     74           dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
     75           closingTagExists(cm, tagName, pos, state, true))
     76         return CodeMirror.Pass;
     77 
     78       var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
     79       replacements[i] = {indent: indent,
     80                          text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
     81                          newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
     82     }
     83 
     84     for (var i = ranges.length - 1; i >= 0; i--) {
     85       var info = replacements[i];
     86       cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
     87       var sel = cm.listSelections().slice(0);
     88       sel[i] = {head: info.newPos, anchor: info.newPos};
     89       cm.setSelections(sel);
     90       if (info.indent) {
     91         cm.indentLine(info.newPos.line, null, true);
     92         cm.indentLine(info.newPos.line + 1, null, true);
     93       }
     94     }
     95   }
     96 
     97   function autoCloseCurrent(cm, typingSlash) {
     98     var ranges = cm.listSelections(), replacements = [];
     99     var head = typingSlash ? "/" : "</";
    100     for (var i = 0; i < ranges.length; i++) {
    101       if (!ranges[i].empty()) return CodeMirror.Pass;
    102       var pos = ranges[i].head, tok = cm.getTokenAt(pos);
    103       var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
    104       if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
    105                           tok.start != pos.ch - 1))
    106         return CodeMirror.Pass;
    107       // Kludge to get around the fact that we are not in XML mode
    108       // when completing in JS/CSS snippet in htmlmixed mode. Does not
    109       // work for other XML embedded languages (there is no general
    110       // way to go from a mixed mode to its current XML state).
    111       var replacement;
    112       if (inner.mode.name != "xml") {
    113         if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
    114           replacement = head + "script";
    115         else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
    116           replacement = head + "style";
    117         else
    118           return CodeMirror.Pass;
    119       } else {
    120         if (!state.context || !state.context.tagName ||
    121             closingTagExists(cm, state.context.tagName, pos, state))
    122           return CodeMirror.Pass;
    123         replacement = head + state.context.tagName;
    124       }
    125       if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
    126       replacements[i] = replacement;
    127     }
    128     cm.replaceSelections(replacements);
    129     ranges = cm.listSelections();
    130     for (var i = 0; i < ranges.length; i++)
    131       if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
    132         cm.indentLine(ranges[i].head.line);
    133   }
    134 
    135   function autoCloseSlash(cm) {
    136     if (cm.getOption("disableInput")) return CodeMirror.Pass;
    137     return autoCloseCurrent(cm, true);
    138   }
    139 
    140   CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
    141 
    142   function indexOf(collection, elt) {
    143     if (collection.indexOf) return collection.indexOf(elt);
    144     for (var i = 0, e = collection.length; i < e; ++i)
    145       if (collection[i] == elt) return i;
    146     return -1;
    147   }
    148 
    149   // If xml-fold is loaded, we use its functionality to try and verify
    150   // whether a given tag is actually unclosed.
    151   function closingTagExists(cm, tagName, pos, state, newTag) {
    152     if (!CodeMirror.scanForClosingTag) return false;
    153     var end = Math.min(cm.lastLine() + 1, pos.line + 500);
    154     var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
    155     if (!nextClose || nextClose.tag != tagName) return false;
    156     var cx = state.context;
    157     // If the immediate wrapping context contains onCx instances of
    158     // the same tag, a closing tag only exists if there are at least
    159     // that many closing tags of that type following.
    160     for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
    161     pos = nextClose.to;
    162     for (var i = 1; i < onCx; i++) {
    163       var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
    164       if (!next || next.tag != tagName) return false;
    165       pos = next.to;
    166     }
    167     return true;
    168   }
    169 });