closetag.js 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. /**
  2. * Tag-closer extension for CodeMirror.
  3. *
  4. * This extension adds an "autoCloseTags" option that can be set to
  5. * either true to get the default behavior, or an object to further
  6. * configure its behavior.
  7. *
  8. * These are supported options:
  9. *
  10. * `whenClosing` (default true)
  11. * Whether to autoclose when the '/' of a closing tag is typed.
  12. * `whenOpening` (default true)
  13. * Whether to autoclose the tag when the final '>' of an opening
  14. * tag is typed.
  15. * `dontCloseTags` (default is empty tags for HTML, none for XML)
  16. * An array of tag names that should not be autoclosed.
  17. * `indentTags` (default is block tags for HTML, none for XML)
  18. * An array of tag names that should, when opened, cause a
  19. * blank line to be added inside the tag, and the blank line and
  20. * closing line to be indented.
  21. *
  22. * See demos/closetag.html for a usage example.
  23. */
  24. (function() {
  25. CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
  26. if (val && (old == CodeMirror.Init || !old)) {
  27. var map = {name: "autoCloseTags"};
  28. if (typeof val != "object" || val.whenClosing)
  29. map["'/'"] = function(cm) { return autoCloseTag(cm, '/'); };
  30. if (typeof val != "object" || val.whenOpening)
  31. map["'>'"] = function(cm) { return autoCloseTag(cm, '>'); };
  32. cm.addKeyMap(map);
  33. } else if (!val && (old != CodeMirror.Init && old)) {
  34. cm.removeKeyMap("autoCloseTags");
  35. }
  36. });
  37. var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
  38. "source", "track", "wbr"];
  39. var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
  40. "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
  41. function autoCloseTag(cm, ch) {
  42. var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
  43. var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
  44. if (inner.mode.name != "xml") return CodeMirror.Pass;
  45. var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
  46. var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
  47. var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
  48. if (ch == ">" && state.tagName) {
  49. var tagName = state.tagName;
  50. if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
  51. var lowerTagName = tagName.toLowerCase();
  52. // Don't process the '>' at the end of an end-tag or self-closing tag
  53. if (tok.type == "tag" && state.type == "closeTag" || tok.string.indexOf("/") > -1 ||
  54. dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1)
  55. return CodeMirror.Pass;
  56. var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1;
  57. var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1);
  58. cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "</" + tagName + ">",
  59. {head: curPos, anchor: curPos});
  60. if (doIndent) {
  61. cm.indentLine(pos.line + 1);
  62. cm.indentLine(pos.line + 2);
  63. }
  64. return;
  65. } else if (ch == "/" && tok.string == "<") {
  66. var tagName = state.context && state.context.tagName;
  67. if (tagName) cm.replaceSelection("/" + tagName + ">", "end");
  68. return;
  69. }
  70. return CodeMirror.Pass;
  71. }
  72. function indexOf(collection, elt) {
  73. if (collection.indexOf) return collection.indexOf(elt);
  74. for (var i = 0, e = collection.length; i < e; ++i)
  75. if (collection[i] == elt) return i;
  76. return -1;
  77. }
  78. })();