Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / 3rdParty / codemirror / addon / edit / closetag.js
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
25 (function(mod) {
26 if (typeof exports == "object" && typeof module == "object") // CommonJS
27 mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
28 else if (typeof define == "function" && define.amd) // AMD
29 define(["../../lib/codemirror", "../fold/xml-fold"], mod);
30 else // Plain browser env
31 mod(CodeMirror);
32 })(function(CodeMirror) {
33 CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
34 if (old != CodeMirror.Init && old)
35 cm.removeKeyMap("autoCloseTags");
36 if (!val) return;
37 var map = {name: "autoCloseTags"};
38 if (typeof val != "object" || val.whenClosing)
39 map["'/'"] = function(cm) { return autoCloseSlash(cm); };
40 if (typeof val != "object" || val.whenOpening)
41 map["'>'"] = function(cm) { return autoCloseGT(cm); };
42 cm.addKeyMap(map);
43 });
44
45 var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
46 "source", "track", "wbr"];
47 var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
48 "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
49
50 function autoCloseGT(cm) {
51 if (cm.getOption("disableInput")) return CodeMirror.Pass;
52 var ranges = cm.listSelections(), replacements = [];
53 for (var i = 0; i < ranges.length; i++) {
54 if (!ranges[i].empty()) return CodeMirror.Pass;
55 var pos = ranges[i].head, tok = cm.getTokenAt(pos);
56 var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
57 if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
58 var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
59 var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
60 var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
61
62 var tagName = state.tagName;
63 if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
64 var lowerTagName = tagName.toLowerCase();
65 // Don't process the '>' at the end of an end-tag or self-closing tag
66 if (!tagName ||
67 tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
68 tok.type == "tag" && state.type == "closeTag" ||
69 tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
70 dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
71 CodeMirror.scanForClosingTag && CodeMirror.scanForClosingTag(cm, pos, tagName,
72 Math.min(cm.lastLine() + 1, pos.line + 50)))
73 return CodeMirror.Pass;
74
75 var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
76 replacements[i] = {indent: indent,
77 text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
78 newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
79 }
80
81 for (var i = ranges.length - 1; i >= 0; i--) {
82 var info = replacements[i];
83 cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
84 var sel = cm.listSelections().slice(0);
85 sel[i] = {head: info.newPos, anchor: info.newPos};
86 cm.setSelections(sel);
87 if (info.indent) {
88 cm.indentLine(info.newPos.line, null, true);
89 cm.indentLine(info.newPos.line + 1, null, true);
90 }
91 }
92 }
93
94 function autoCloseSlash(cm) {
95 if (cm.getOption("disableInput")) return CodeMirror.Pass;
96 var ranges = cm.listSelections(), replacements = [];
97 for (var i = 0; i < ranges.length; i++) {
98 if (!ranges[i].empty()) return CodeMirror.Pass;
99 var pos = ranges[i].head, tok = cm.getTokenAt(pos);
100 var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
101 if (tok.type == "string" || tok.string.charAt(0) != "<" ||
102 tok.start != pos.ch - 1 || inner.mode.name != "xml" ||
103 !state.context || !state.context.tagName)
104 return CodeMirror.Pass;
105 replacements[i] = "/" + state.context.tagName + ">";
106 }
107 cm.replaceSelections(replacements);
108 }
109
110 function indexOf(collection, elt) {
111 if (collection.indexOf) return collection.indexOf(elt);
112 for (var i = 0, e = collection.length; i < e; ++i)
113 if (collection[i] == elt) return i;
114 return -1;
115 }
116 });