aboutsummaryrefslogtreecommitdiff
path: root/imports/codemirror/lib/util/closetag.js
diff options
context:
space:
mode:
Diffstat (limited to 'imports/codemirror/lib/util/closetag.js')
-rw-r--r--imports/codemirror/lib/util/closetag.js174
1 files changed, 174 insertions, 0 deletions
diff --git a/imports/codemirror/lib/util/closetag.js b/imports/codemirror/lib/util/closetag.js
new file mode 100644
index 00000000..44c2b435
--- /dev/null
+++ b/imports/codemirror/lib/util/closetag.js
@@ -0,0 +1,174 @@
1/**
2 * Tag-closer extension for CodeMirror.
3 *
4 * This extension adds a "closeTag" utility function that can be used with key bindings to
5 * insert a matching end tag after the ">" character of a start tag has been typed. It can
6 * also complete "</" if a matching start tag is found. It will correctly ignore signal
7 * characters for empty tags, comments, CDATA, etc.
8 *
9 * The function depends on internal parser state to identify tags. It is compatible with the
10 * following CodeMirror modes and will ignore all others:
11 * - htmlmixed
12 * - xml
13 * - xmlpure
14 *
15 * See demos/closetag.html for a usage example.
16 *
17 * @author Nathan Williams <nathan@nlwillia.net>
18 * Contributed under the same license terms as CodeMirror.
19 */
20(function() {
21 /** Option that allows tag closing behavior to be toggled. Default is true. */
22 CodeMirror.defaults['closeTagEnabled'] = true;
23
24 /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */
25 CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul'];
26
27 /**
28 * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass.
29 * - cm: The editor instance.
30 * - ch: The character being processed.
31 * - indent: Optional. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option.
32 * Pass false to disable indentation. Pass an array to override the default list of tag names.
33 */
34 CodeMirror.defineExtension("closeTag", function(cm, ch, indent) {
35 if (!cm.getOption('closeTagEnabled')) {
36 throw CodeMirror.Pass;
37 }
38
39 var mode = cm.getOption('mode');
40
41 if (mode == 'text/html') {
42
43 /*
44 * Relevant structure of token:
45 *
46 * htmlmixed
47 * className
48 * state
49 * htmlState
50 * type
51 * context
52 * tagName
53 * mode
54 *
55 * xml
56 * className
57 * state
58 * tagName
59 * type
60 */
61
62 var pos = cm.getCursor();
63 var tok = cm.getTokenAt(pos);
64 var state = tok.state;
65
66 if (state.mode && state.mode != 'html') {
67 throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode.
68 }
69
70 if (ch == '>') {
71 var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
72
73 if (tok.className == 'tag' && type == 'closeTag') {
74 throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag.
75 }
76
77 cm.replaceSelection('>'); // Mode state won't update until we finish the tag.
78 pos = {line: pos.line, ch: pos.ch + 1};
79 cm.setCursor(pos);
80
81 tok = cm.getTokenAt(cm.getCursor());
82 state = tok.state;
83 type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml
84
85 if (tok.className == 'tag' && type != 'selfcloseTag') {
86 var tagName = state.htmlState ? state.htmlState.context.tagName : state.tagName; // htmlmixed : xml
87 if (tagName.length > 0) {
88 insertEndTag(cm, indent, pos, tagName);
89 }
90 return;
91 }
92
93 // Undo the '>' insert and allow cm to handle the key instead.
94 cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos);
95 cm.replaceSelection("");
96
97 } else if (ch == '/') {
98 if (tok.className == 'tag' && tok.string == '<') {
99 var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : state.context.tagName; // htmlmixed : xml # extra htmlmized check is for '</' edge case
100 if (tagName.length > 0) {
101 completeEndTag(cm, pos, tagName);
102 return;
103 }
104 }
105 }
106
107 } else if (mode == 'xmlpure') {
108
109 var pos = cm.getCursor();
110 var tok = cm.getTokenAt(pos);
111 var tagName = tok.state.context.tagName;
112
113 if (ch == '>') {
114 // <foo> tagName=foo, string=foo
115 // <foo /> tagName=foo, string=/ # ignore
116 // <foo></foo> tagName=foo, string=/foo # ignore
117 if (tok.string == tagName) {
118 cm.replaceSelection('>'); // parity w/html modes
119 pos = {line: pos.line, ch: pos.ch + 1};
120 cm.setCursor(pos);
121
122 insertEndTag(cm, indent, pos, tagName);
123 return;
124 }
125
126 } else if (ch == '/') {
127 // <foo / tagName=foo, string= # ignore
128 // <foo></ tagName=foo, string=<
129 if (tok.string == '<') {
130 completeEndTag(cm, pos, tagName);
131 return;
132 }
133 }
134 }
135
136 throw CodeMirror.Pass; // Bubble if not handled
137 });
138
139 function insertEndTag(cm, indent, pos, tagName) {
140 if (shouldIndent(cm, indent, tagName)) {
141 cm.replaceSelection('\n\n</' + tagName + '>', 'end');
142 cm.indentLine(pos.line + 1);
143 cm.indentLine(pos.line + 2);
144 cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length});
145 } else {
146 cm.replaceSelection('</' + tagName + '>');
147 cm.setCursor(pos);
148 }
149 }
150
151 function shouldIndent(cm, indent, tagName) {
152 if (typeof indent == 'undefined' || indent == null || indent == true) {
153 indent = cm.getOption('closeTagIndent');
154 }
155 if (!indent) {
156 indent = [];
157 }
158 return indexOf(indent, tagName.toLowerCase()) != -1;
159 }
160
161 // C&P from codemirror.js...would be nice if this were visible to utilities.
162 function indexOf(collection, elt) {
163 if (collection.indexOf) return collection.indexOf(elt);
164 for (var i = 0, e = collection.length; i < e; ++i)
165 if (collection[i] == elt) return i;
166 return -1;
167 }
168
169 function completeEndTag(cm, pos, tagName) {
170 cm.replaceSelection('/' + tagName + '>');
171 cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 });
172 }
173
174})();