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