~ubuntu-branches/ubuntu/utopic/codemirror-js/utopic

« back to all changes in this revision

Viewing changes to lib/util/closetag.js

  • Committer: Package Import Robot
  • Author(s): David Paleino
  • Date: 2012-04-12 12:25:28 UTC
  • Revision ID: package-import@ubuntu.com-20120412122528-8xp5a8frj4h1d3ee
Tags: upstream-2.23
ImportĀ upstreamĀ versionĀ 2.23

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
})();