diff options
author | Jose Antonio Marquez | 2012-05-04 14:00:54 -0700 |
---|---|---|
committer | Jose Antonio Marquez | 2012-05-04 14:00:54 -0700 |
commit | 9c0bda09a502472768f6dd5090a882d11be58d23 (patch) | |
tree | 28eb75165553daa979be3bc9223d35d875a008dc /imports/codemirror/lib/util/closetag.js | |
parent | 2456f0d60f642016135aa0f96126cb3249343757 (diff) | |
parent | fec9ccee11ea21ffc95edce6e89d0d302b63e3d8 (diff) | |
download | ninja-9c0bda09a502472768f6dd5090a882d11be58d23.tar.gz |
Merge branch 'refs/heads/Ninja-Internal' into Document
Diffstat (limited to 'imports/codemirror/lib/util/closetag.js')
-rw-r--r-- | imports/codemirror/lib/util/closetag.js | 174 |
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 | })(); | ||