2 * Tag-closer extension for CodeMirror.
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.
8 * These are supported options:
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
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.
22 * See demos/closetag.html for a usage example.
26 CodeMirror
.defineOption("autoCloseTags", false, function(cm
, val
, old
) {
27 if (val
&& (old
== CodeMirror
.Init
|| !old
)) {
28 var map
= {name
: "autoCloseTags"};
29 if (typeof val
!= "object" || val
.whenClosing
)
30 map
["'/'"] = function(cm
) { return autoCloseTag(cm
, '/'); };
31 if (typeof val
!= "object" || val
.whenOpening
)
32 map
["'>'"] = function(cm
) { return autoCloseTag(cm
, '>'); };
34 } else if (!val
&& (old
!= CodeMirror
.Init
&& old
)) {
35 cm
.removeKeyMap("autoCloseTags");
39 var htmlDontClose
= ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
40 "source", "track", "wbr"];
41 var htmlIndent
= ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
42 "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
44 function autoCloseTag(cm
, ch
) {
45 var pos
= cm
.getCursor(), tok
= cm
.getTokenAt(pos
);
46 var inner
= CodeMirror
.innerMode(cm
.getMode(), tok
.state
), state
= inner
.state
;
47 if (inner
.mode
.name
!= "xml") return CodeMirror
.Pass
;
49 var opt
= cm
.getOption("autoCloseTags"), html
= inner
.mode
.configuration
== "html";
50 var dontCloseTags
= (typeof opt
== "object" && opt
.dontCloseTags
) || (html
&& htmlDontClose
);
51 var indentTags
= (typeof opt
== "object" && opt
.indentTags
) || (html
&& htmlIndent
);
53 if (ch
== ">" && state
.tagName
) {
54 var tagName
= state
.tagName
;
55 if (tok
.end
> pos
.ch
) tagName
= tagName
.slice(0, tagName
.length
- tok
.end
+ pos
.ch
);
56 var lowerTagName
= tagName
.toLowerCase();
57 // Don't process the '>' at the end of an end-tag or self-closing tag
58 if (tok
.type
== "tag" && state
.type
== "closeTag" ||
59 tok
.string
.indexOf("/") == (tok
.string
.length
- 1) || // match something like <someTagName />
60 dontCloseTags
&& indexOf(dontCloseTags
, lowerTagName
) > -1)
61 return CodeMirror
.Pass
;
63 var doIndent
= indentTags
&& indexOf(indentTags
, lowerTagName
) > -1;
64 var curPos
= doIndent
? CodeMirror
.Pos(pos
.line
+ 1, 0) : CodeMirror
.Pos(pos
.line
, pos
.ch
+ 1);
65 cm
.replaceSelection(">" + (doIndent
? "\n\n" : "") + "</" + tagName
+ ">",
66 {head
: curPos
, anchor
: curPos
});
68 cm
.indentLine(pos
.line
+ 1);
69 cm
.indentLine(pos
.line
+ 2);
72 } else if (ch
== "/" && tok
.string
== "<") {
73 var tagName
= state
.context
&& state
.context
.tagName
;
74 if (tagName
) cm
.replaceSelection("/" + tagName
+ ">", "end");
77 return CodeMirror
.Pass
;
80 function indexOf(collection
, elt
) {
81 if (collection
.indexOf
) return collection
.indexOf(elt
);
82 for (var i
= 0, e
= collection
.length
; i
< e
; ++i
)
83 if (collection
[i
] == elt
) return i
;