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 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
32 })(function(CodeMirror
) {
33 CodeMirror
.defineOption("autoCloseTags", false, function(cm
, val
, old
) {
34 if (old
!= CodeMirror
.Init
&& old
)
35 cm
.removeKeyMap("autoCloseTags");
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
); };
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"];
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
);
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
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
;
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)};
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
);
88 cm
.indentLine(info
.newPos
.line
, null, true);
89 cm
.indentLine(info
.newPos
.line
+ 1, null, true);
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
+ ">";
107 cm
.replaceSelections(replacements
);
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
;