Permalink
Browse files

[util/closetag] Rewrite using defineOption

Closes #995
  • Loading branch information...
1 parent 1140306 commit 4052fbf381d94c919c7dd854c9affd99c50e2429 @marijnh marijnh committed Nov 29, 2012
Showing with 84 additions and 132 deletions.
  1. +1 −4 demo/closetag.html
  2. +4 −1 doc/manual.html
  3. +6 −2 lib/codemirror.js
  4. +61 −121 lib/util/closetag.js
  5. +6 −4 mode/xml/xml.js
  6. +6 −0 test/test.js
View
@@ -30,10 +30,7 @@
<script type="text/javascript">
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: 'text/html',
- extraKeys: {
- "'>'": function(cm) { CodeMirror.closeTag(cm, '>'); },
- "'/'": function(cm) { CodeMirror.closeTag(cm, '/'); }
- }
+ autoCloseTags: true
});
</script>
</body>
View
@@ -663,7 +663,10 @@ <h2 id="api">Programming API</h2>
than those added later.</dd>
<dt id="removeKeyMap"><code>removeKeyMap(map)</code></dt>
<dd>Disable a keymap added
- with <a href="#addKeyMap"><code>addKeyMap</code></a>.</dd>
+ with <a href="#addKeyMap"><code>addKeyMap</code></a>. Either
+ pass in the keymap object itself, or a string, which will be
+ compared against the <code>name</code> property of the active
+ keymaps.</dd>
<dt id="on"><code>on(type, func)</code></dt>
<dd>Register an event handler for the given event type (a
View
@@ -2312,8 +2312,12 @@ window.CodeMirror = (function() {
},
removeKeyMap: function(map) {
- var i = indexOf(this.view.keyMaps, map);
- if (i > -1) this.view.keyMaps.splice(i, 1);
+ var maps = this.view.keyMaps;
+ for (var i = 0; i < maps.length; ++i)
+ if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
+ maps.splice(i, 1);
+ return true;
+ }
},
undo: operation(null, function() {unredoHelper(this, "undo");}),
View
@@ -1,145 +1,85 @@
/**
* Tag-closer extension for CodeMirror.
*
- * This extension adds a "closeTag" utility function that can be used with key bindings to
- * insert a matching end tag after the ">" character of a start tag has been typed. It can
- * also complete "</" if a matching start tag is found. It will correctly ignore signal
- * characters for empty tags, comments, CDATA, etc.
+ * This extension adds an "autoCloseTags" option that can be set to
+ * either true to get the default behavior, or an object to further
+ * configure its behavior.
*
- * The function depends on internal parser state to identify tags. It is compatible with the
- * following CodeMirror modes and will ignore all others:
- * - htmlmixed
- * - xml
+ * These are supported options:
+ *
+ * `whenClosing` (default true)
+ * Whether to autoclose when the '/' of a closing tag is typed.
+ * `whenOpening` (default true)
+ * Whether to autoclose the tag when the final '>' of an opening
+ * tag is typed.
+ * `dontCloseTags` (default is empty tags for HTML, none for XML)
+ * An array of tag names that should not be autoclosed.
+ * `indentTags` (default is block tags for HTML, none for XML)
+ * An array of tag names that should, when opened, cause a
+ * blank line to be added inside the tag, and the blank line and
+ * closing line to be indented.
*
* See demos/closetag.html for a usage example.
- *
- * @author Nathan Williams <nathan@nlwillia.net>
- * Contributed under the same license terms as CodeMirror.
*/
(function() {
- /** Option that allows tag closing behavior to be toggled. Default is true. */
- CodeMirror.defaults['closeTagEnabled'] = true;
-
- /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */
- 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'];
-
- /** Array of tag names where an end tag is forbidden. */
- CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
+ CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
+ if (val && (old == CodeMirror.Init || !old)) {
+ var map = {name: "autoCloseTags"};
+ if (typeof val != "object" || val.whenClosing)
+ map["'/'"] = function(cm) { autoCloseTag(cm, '/'); }
+ if (typeof val != "object" || val.whenOpening)
+ map["'>'"] = function(cm) { autoCloseTag(cm, '>'); }
+ cm.addKeyMap(map);
+ } else if (!val && (old != CodeMirror.Init && old)) {
+ cm.removeKeyMap("autoCloseTags");
+ }
+ });
- function innerXMLState(cm, state) {
- var inner = CodeMirror.innerMode(cm.getMode(), state);
- if (inner.mode.name == "xml") return inner.state;
- }
+ var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
+ "source", "track", "wbr"];
+ var htmlIndent = ["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"];
+ function autoCloseTag(cm, ch) {
+ var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
+ var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
+ if (inner.mode.name != "xml") throw CodeMirror.Pass;
- /**
- * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass.
- * - cm: The editor instance.
- * - ch: The character being processed.
- * - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option.
- * Pass false to disable indentation. Pass an array to override the default list of tag names.
- * - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode.
- */
- CodeMirror.defineExtension("closeTag", function(cm, ch, indent, vd) {
- if (!cm.getOption('closeTagEnabled')) {
- throw CodeMirror.Pass;
- }
- var pos = cm.getCursor();
- var tok = cm.getTokenAt(pos);
- var state = innerXMLState(cm, tok.state);
+ var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
+ var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
+ var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
- if (state) {
-
- if (ch == '>') {
- var type = state.type;
-
- if (tok.type == 'tag' && type == 'closeTag') {
- throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag.
- }
-
- cm.replaceSelection('>'); // Mode state won't update until we finish the tag.
- pos = {line: pos.line, ch: pos.ch + 1};
- cm.setCursor(pos);
-
- tok = cm.getTokenAt(cm.getCursor());
- state = innerXMLState(cm, tok.state);
- if (!state) throw CodeMirror.Pass;
- var type = state.type;
+ if (ch == ">" && state.tagName) {
+ var tagName = state.tagName;
+ if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
+ var lowerTagName = tagName.toLowerCase();
+ // Don't process the '>' at the end of an end-tag or self-closing tag
+ if (tok.type == "tag" && state.type == "closeTag" ||
+ /\/\s*$/.test(tok.string) ||
+ dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1)
+ throw CodeMirror.Pass;
- if (tok.type == 'tag' && type != 'selfcloseTag') {
- var tagName = state.tagName;
- if (tagName.length > 0 && shouldClose(cm, vd, tagName)) {
- insertEndTag(cm, indent, pos, tagName);
- }
- return;
- }
-
- // Undo the '>' insert and allow cm to handle the key instead.
- cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos);
- cm.replaceSelection("");
-
- } else if (ch == '/') {
- if (tok.type == 'tag' && tok.string == '<') {
- var ctx = state.context, tagName = ctx ? ctx.tagName : '';
- if (tagName.length > 0) {
- completeEndTag(cm, pos, tagName);
- return;
- }
- }
+ var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1;
+ cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "</" + tagName + ">",
+ doIndent ? {line: pos.line + 1, ch: 0} : {line: pos.line, ch: pos.ch + 1});
+ if (doIndent) {
+ cm.indentLine(pos.line + 1);
+ cm.indentLine(pos.line + 2);
}
-
- }
-
- throw CodeMirror.Pass; // Bubble if not handled
- });
-
- function insertEndTag(cm, indent, pos, tagName) {
- if (shouldIndent(cm, indent, tagName)) {
- cm.replaceSelection('\n\n</' + tagName + '>', 'end');
- cm.indentLine(pos.line + 1);
- cm.indentLine(pos.line + 2);
- cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length});
- } else {
- cm.replaceSelection('</' + tagName + '>');
- cm.setCursor(pos);
+ return;
+ } else if (ch == "/" && tok.type == "tag" && tok.string == "<") {
+ var tagName = state.context && state.context.tagName;
+ if (tagName) cm.replaceSelection("/" + tagName + ">", "end");
+ return;
}
+ throw CodeMirror.Pass;
}
-
- function shouldIndent(cm, indent, tagName) {
- if (typeof indent == 'undefined' || indent == null || indent == true) {
- indent = cm.getOption('closeTagIndent');
- }
- if (!indent) {
- indent = [];
- }
- return indexOf(indent, tagName.toLowerCase()) != -1;
- }
-
- function shouldClose(cm, vd, tagName) {
- if (cm.getOption('mode') == 'xml') {
- return true; // always close xml tags
- }
- if (typeof vd == 'undefined' || vd == null) {
- vd = cm.getOption('closeTagVoid');
- }
- if (!vd) {
- vd = [];
- }
- return indexOf(vd, tagName.toLowerCase()) == -1;
- }
-
- // C&P from codemirror.js...would be nice if this were visible to utilities.
+
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
if (collection[i] == elt) return i;
return -1;
}
-
- function completeEndTag(cm, pos, tagName) {
- cm.replaceSelection('/' + tagName + '>');
- cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 });
- }
-
})();
View
@@ -211,14 +211,16 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
}
function endtag(startOfLine) {
return function(type) {
+ var tagName = curState.tagName;
+ curState.tagName = null;
if (type == "selfcloseTag" ||
- (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
- maybePopContext(curState.tagName.toLowerCase());
+ (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) {
+ maybePopContext(tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
- maybePopContext(curState.tagName.toLowerCase());
- pushContext(curState.tagName, startOfLine);
+ maybePopContext(tagName.toLowerCase());
+ pushContext(tagName, startOfLine);
return cont();
}
return cont();
View
@@ -1158,4 +1158,10 @@ testCM("addKeyMap", function(cm) {
sendKey(39);
eq(test, 12);
eqPos(cm.getCursor(), {line: 0, ch: 2});
+ cm.addKeyMap({Right: function() { test = 55; }, name: "mymap"});
+ sendKey(39);
+ eq(test, 55);
+ cm.removeKeyMap("mymap");
+ sendKey(39);
+ eqPos(cm.getCursor(), {line: 0, ch: 3});
}, {value: "abc"});

0 comments on commit 4052fbf

Please sign in to comment.