diff --git a/lib/ace/mode/folding/html.js b/lib/ace/mode/folding/html.js
index 082ab010..761cfb72 100644
--- a/lib/ace/mode/folding/html.js
+++ b/lib/ace/mode/folding/html.js
@@ -44,6 +44,7 @@ var CStyleFoldMode = require("./cstyle").FoldMode;
var FoldMode = exports.FoldMode = function() {
MixedFoldMode.call(this, new XmlFoldMode({
+ // void elements
"area": 1,
"base": 1,
"br": 1,
@@ -59,7 +60,20 @@ var FoldMode = exports.FoldMode = function() {
"param": 1,
"source": 1,
"track": 1,
- "wbr": 1
+ "wbr": 1,
+
+ // optional tags
+ "li": 1,
+ "dt": 1,
+ "dd": 1,
+ "p": 1,
+ "rt": 1,
+ "rp": 1,
+ "optgroup": 1,
+ "option": 1,
+ "colgroup": 1,
+ "td": 1,
+ "th": 1
}), {
"js-": new CStyleFoldMode(),
"css-": new CStyleFoldMode()
diff --git a/lib/ace/mode/folding/html_test.js b/lib/ace/mode/folding/html_test.js
index f5be9300..3179a39e 100644
--- a/lib/ace/mode/folding/html_test.js
+++ b/lib/ace/mode/folding/html_test.js
@@ -40,8 +40,6 @@ if (typeof process !== "undefined")
define(function(require, exports, module) {
-var FoldMode = require("./mixed").FoldMode;
-var XmlFoldMode = require("./xml").FoldMode;
var HtmlMode = require("../html").Mode;
var EditSession = require("../../edit_session").EditSession;
var assert = require("../../test/assertions");
@@ -59,6 +57,7 @@ module.exports = {
var mode = new HtmlMode();
session.setMode(mode);
+ session.setFoldStyle("markbeginend");
assert.equal(session.getFoldWidget(0), "start");
assert.equal(session.getFoldWidget(1), "start");
@@ -84,6 +83,7 @@ module.exports = {
var mode = new HtmlMode();
session.setMode(mode);
+ session.setFoldStyle("markbeginend");
assert.equal(session.getFoldWidget(0), "start");
assert.equal(session.getFoldWidget(1), "start");
@@ -96,11 +96,73 @@ module.exports = {
assert.range(session.getFoldWidgetRange(1), 1, 17, 3, 4);
assert.range(session.getFoldWidgetRange(3), 1, 17, 3, 4);
- }
+ },
+ "test: fold should skip self closing elements": function() {
+ var session = new EditSession([
+ '
',
+ '
',
+ ''
+ ]);
+
+ var mode = new HtmlMode();
+ session.setMode(mode);
+ session.setFoldStyle("markbeginend");
+
+ assert.equal(session.getFoldWidget(0), "start");
+ assert.equal(session.getFoldWidget(1), "");
+ assert.equal(session.getFoldWidget(2), "end");
+
+ assert.range(session.getFoldWidgetRange(0), 0, 7, 2, 0);
+ assert.range(session.getFoldWidgetRange(2), 0, 7, 2, 0);
+ },
+
+ "test: fold should skip void elements": function() {
+ var session = new EditSession([
+ '',
+ '
',
+ ''
+ ]);
+
+ var mode = new HtmlMode();
+ session.setMode(mode);
+ session.setFoldStyle("markbeginend");
+
+ assert.equal(session.getFoldWidget(0), "start");
+ assert.equal(session.getFoldWidget(1), "");
+ assert.equal(session.getFoldWidget(2), "end");
+
+ assert.range(session.getFoldWidgetRange(0), 0, 7, 2, 0);
+ assert.range(session.getFoldWidgetRange(2), 0, 7, 2, 0);
+ },
+
+ "test: fold multiple unclosed elements": function() {
+ var session = new EditSession([
+ '',
+ '
',
+ 'juhu',
+ '
',
+ 'kinners',
+ '
'
+ ]);
+
+ var mode = new HtmlMode();
+ session.setMode(mode);
+ session.setFoldStyle("markbeginend");
+
+ assert.equal(session.getFoldWidget(0), "start");
+ assert.equal(session.getFoldWidget(1), "");
+ assert.equal(session.getFoldWidget(2), "");
+ assert.equal(session.getFoldWidget(3), "");
+ assert.equal(session.getFoldWidget(4), "");
+ assert.equal(session.getFoldWidget(5), "end");
+
+ assert.range(session.getFoldWidgetRange(0), 0, 6, 5, 0);
+ assert.range(session.getFoldWidgetRange(5), 0, 6, 5, 0);
+ }
};
});
-if (typeof process !== "undefined")
+if (typeof module !== "undefined" && module === require.main)
require("asyncjs").test.testcase(module.exports).exec();
\ No newline at end of file
diff --git a/lib/ace/mode/folding/xml.js b/lib/ace/mode/folding/xml.js
index 5da1afd2..c08ed01d 100644
--- a/lib/ace/mode/folding/xml.js
+++ b/lib/ace/mode/folding/xml.js
@@ -62,14 +62,16 @@ oop.inherits(FoldMode, BaseFoldMode);
.trim()
.replace(/^<|>$|\s+/g, "")
.split("><");
-
var fold = tags[0];
if (!fold || this.voidElements[fold])
return "";
if (fold.charAt(0) == "/")
- return "end";
+ return foldStyle == "markbeginend" ? "end" : "";
+
+ if (fold.charAt(fold.length-1) == "/")
+ return "";
if (tags.indexOf("/" + fold) !== -1)
return "";
@@ -80,44 +82,36 @@ oop.inherits(FoldMode, BaseFoldMode);
this.getFoldWidgetRange = function(session, foldStyle, row) {
var start, end;
var stack = [];
+ var voidElements = this.voidElements;
var iterator = new TokenIterator(session, row, 0);
var step = "stepForward";
var isBack = false;
- // http://dev.w3.org/html5/spec/syntax.html#optional-tags
- // TODO
-// var optionalTags = {
-// "html": 1,
-// "head": 1,
-// "body": 1,
-// "li": 1,
-// "dt": 1,
-// "dd": 1,
-// "p": 1,
-// "rt": 1,
-// "rp": 1,
-// "optgroup": 1,
-// "option": 1,
-// "colgroup": 1,
-// "thead": 1,
-// "tbody": 1,
-// "tfoot": 1,
-// "tr": 1,
-// "td": 1,
-// "th": 1
-// };
+ function pop(stack, tagName) {
+ while (stack.length) {
+ var top = stack[stack.length-1];
+ if (!tagName || top === "" || top == tagName) {
+ stack.pop();
+ return true;
+ }
+ if (voidElements[top]) {
+ stack.pop();
+ continue;
+ }
+ else
+ return false;
+ }
+ return false;
+ }
// limited XML parsing to find matching tag
do {
var token = iterator.getCurrentToken();
-
+
var value = token.value.trim();
if (token && token.type == "meta.tag" && token.value !== ">") {
var tagName = value.replace(/^[<\s]*|[\s*>]$/g, "");
- if (this.voidElements[tagName])
- continue;
-
if (!start) {
if (tagName.charAt(0) == "/") {
tagName = tagName.slice(1);
@@ -130,7 +124,6 @@ oop.inherits(FoldMode, BaseFoldMode);
column: iterator.getCurrentTokenColumn() + (isBack ? 0 : value.length + 1)
};
-// console.log("push", tagName)
stack.push(tagName);
}
else {
@@ -139,13 +132,15 @@ oop.inherits(FoldMode, BaseFoldMode);
tagName = tagName.slice(1);
close = !isBack;
}
+ else if (tagName.charAt(tagName.length-1) == "/") {
+ tagName = "";
+ close = !isBack;
+ }
else
close = isBack;
-
+
if (close) {
- if (stack[stack.length-1] == tagName) {
-// console.log("pop", tagName)
- stack.pop();
+ if (pop(stack, tagName)) {
if (stack.length === 0) {
end = {
row: iterator.getCurrentTokenRow(),
@@ -158,11 +153,11 @@ oop.inherits(FoldMode, BaseFoldMode);
}
}
else {
- console.error("unmatched tags!", tagName, stack);
+ if (!(isBack && voidElements[tagName]))
+ console.error("unmatched tags!", tagName, stack);
}
}
else {
-// console.log("push", tagName)
stack.push(tagName);
}
}
diff --git a/lib/ace/mode/folding/xml_test.js b/lib/ace/mode/folding/xml_test.js
new file mode 100644
index 00000000..7af1c201
--- /dev/null
+++ b/lib/ace/mode/folding/xml_test.js
@@ -0,0 +1,116 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Ajax.org Code Editor (ACE).
+ *
+ * The Initial Developer of the Original Code is
+ * Ajax.org B.V.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabian Jakobs
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+if (typeof process !== "undefined")
+ require("amd-loader");
+
+define(function(require, exports, module) {
+
+var XmlMode = require("../xml").Mode;
+var EditSession = require("../../edit_session").EditSession;
+var assert = require("../../test/assertions");
+
+module.exports = {
+
+ "test: fold multi line self closing element": function() {
+ var session = new EditSession([
+ ''
+ ]);
+
+ var mode = new XmlMode();
+ session.setFoldStyle("markbeginend");
+ session.setMode(mode);
+
+ assert.equal(session.getFoldWidget(0), "start");
+ assert.equal(session.getFoldWidget(1), "");
+ assert.equal(session.getFoldWidget(2), "end");
+
+ assert.range(session.getFoldWidgetRange(0), 0, 8, 2, 19);
+ assert.range(session.getFoldWidgetRange(2), 0, 8, 2, 19);
+ },
+
+ "test: fold should skip self closing elements": function() {
+ var session = new EditSession([
+ '',
+ ' ',
+ ''
+ ]);
+
+ var mode = new XmlMode();
+ session.setFoldStyle("markbeginend");
+ session.setMode(mode);
+
+ assert.equal(session.getFoldWidget(0), "start");
+ assert.equal(session.getFoldWidget(1), "");
+ assert.equal(session.getFoldWidget(2), "end");
+
+ assert.range(session.getFoldWidgetRange(0), 0, 9, 2, 0);
+ assert.range(session.getFoldWidgetRange(2), 0, 9, 2, 0);
+ },
+
+ "test: fold should skip multi line self closing elements": function() {
+ var session = new EditSession([
+ '',
+ ' ',
+ ''
+ ]);
+
+ var mode = new XmlMode();
+ session.setMode(mode);
+ session.setFoldStyle("markbeginend");
+
+ assert.equal(session.getFoldWidget(0), "start");
+ assert.equal(session.getFoldWidget(1), "start");
+ assert.equal(session.getFoldWidget(2), "");
+ assert.equal(session.getFoldWidget(3), "end");
+ assert.equal(session.getFoldWidget(4), "end");
+
+ assert.range(session.getFoldWidgetRange(0), 0, 9, 4, 0);
+ assert.range(session.getFoldWidgetRange(1), 1, 9, 3, 19);
+ assert.range(session.getFoldWidgetRange(3), 1, 9, 3, 19);
+ assert.range(session.getFoldWidgetRange(4), 0, 9, 4, 0);
+ }
+};
+
+});
+
+if (typeof module !== "undefined" && module === require.main)
+ require("asyncjs").test.testcase(module.exports).exec();
\ No newline at end of file