From 25400848f27d66ffe9c4efcfbed2583aeccc7148 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Thu, 15 Dec 2011 17:23:34 +0100 Subject: [PATCH] fix folding after html mode changes --- lib/ace/mode/folding/html_test.js | 12 +- lib/ace/mode/folding/xml.js | 301 ++++++++++++++++++---------- lib/ace/mode/folding/xml_test.js | 8 +- lib/ace/mode/html_tokenizer_test.js | 21 +- 4 files changed, 218 insertions(+), 124 deletions(-) diff --git a/lib/ace/mode/folding/html_test.js b/lib/ace/mode/folding/html_test.js index 3179a39e..2e92af32 100644 --- a/lib/ace/mode/folding/html_test.js +++ b/lib/ace/mode/folding/html_test.js @@ -113,8 +113,8 @@ module.exports = { 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); + assert.range(session.getFoldWidgetRange(0), 0, 6, 2, 0); + assert.range(session.getFoldWidgetRange(2), 0, 6, 2, 0); }, "test: fold should skip void elements": function() { @@ -132,8 +132,8 @@ module.exports = { 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); + assert.range(session.getFoldWidgetRange(0), 0, 6, 2, 0); + assert.range(session.getFoldWidgetRange(2), 0, 6, 2, 0); }, "test: fold multiple unclosed elements": function() { @@ -157,8 +157,8 @@ module.exports = { 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); + assert.range(session.getFoldWidgetRange(0), 0, 5, 5, 0); + assert.range(session.getFoldWidgetRange(5), 0, 5, 5, 0); } }; diff --git a/lib/ace/mode/folding/xml.js b/lib/ace/mode/folding/xml.js index 97e3c79d..e4af4878 100644 --- a/lib/ace/mode/folding/xml.js +++ b/lib/ace/mode/folding/xml.js @@ -38,6 +38,7 @@ define(function(require, exports, module) { var oop = require("../../lib/oop"); +var lang = require("../../lib/lang"); var Range = require("../../range").Range; var BaseFoldMode = require("./fold_mode").FoldMode; var TokenIterator = require("../../token_iterator").TokenIterator; @@ -51,121 +52,211 @@ oop.inherits(FoldMode, BaseFoldMode); (function() { this.getFoldWidget = function(session, foldStyle, row) { - var tags = session.getTokens(row, row)[0].tokens - .filter(function(token) { - return token.type === "meta.tag"; - }) - .map(function(token) { - return token.value; - }). - join("") - .trim() - .replace(/^<|>$|\s+/g, "") - .split("><"); - var fold = tags[0]; - - if (!fold || this.voidElements[fold]) - return ""; - - if (fold.charAt(0) == "/") + var tag = this._getFirstTagInLine(session, row); + + if (tag.closing) return foldStyle == "markbeginend" ? "end" : ""; - - if (fold.charAt(fold.length-1) == "/") + + if (!tag.tagName || this.voidElements[tag.tagName.toLowerCase()]) return ""; - - if (tags.indexOf("/" + fold) !== -1) + + if (tag.selfClosing) return ""; - + + if (tag.value.indexOf("/" + tag.tagName) !== -1) + return ""; + return "start"; }; - - 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; - - 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; + + this._getFirstTagInLine = function(session, row) { + var tokens = session.getTokens(row, row)[0].tokens; + var value = ""; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (token.type.indexOf("meta.tag") === 0) + value += token.value; + else + value += lang.stringRepeat(" ", token.value.length); } - - // limited XML parsing to find matching tag - do { - var token = iterator.getCurrentToken(); + + return this._parseTag(value); + }; - var value = token.value.trim(); - if (token && token.type == "meta.tag" && token.value !== ">") { - var tagName = value.replace(/^[<\s]*|[\s*>]$/g, ""); - if (!start) { - if (tagName.charAt(0) == "/") { - tagName = tagName.slice(1); - step = "stepBackward"; - isBack = true; - } - - start = { - row: row, - column: iterator.getCurrentTokenColumn() + (isBack ? 0 : value.length + 1) - }; + this.tagRe = /^(\s*)(?)/; + this._parseTag = function(tag) { + + var match = this.tagRe.exec(tag); + var column = this.tagRe.lastIndex || 0; + this.tagRe.lastIndex = 0; - stack.push(tagName); - } - else { - var close; - if (tagName.charAt(0) == "/") { - tagName = tagName.slice(1); - close = !isBack; - } - else if (tagName.charAt(tagName.length-1) == "/") { - tagName = ""; - close = !isBack; - } - else - close = isBack; - - if (close) { - if (pop(stack, tagName)) { - if (stack.length === 0) { - end = { - row: iterator.getCurrentTokenRow(), - column: iterator.getCurrentTokenColumn() + (isBack ? value.length + 1 : 0) - }; - if (isBack) - return Range.fromPoints(end, start); - else - return Range.fromPoints(start, end); - } - } - else { - if (!(isBack && voidElements[tagName])) - typeof console !== undefined && console.error("unmatched tags!", tagName, stack); - } - } - else { - stack.push(tagName); - } - } - } - - } while(token = iterator[step]()); + return { + value: tag, + match: match ? match[2] : "", + closing: match ? !!match[3] : false, + selfClosing: match ? !!match[5] || match[2] == "/>" : false, + tagName: match ? match[4] : "", + column: match[1] ? column + match[1].length : column + }; }; + /** + * reads a full tag and places the iterator after the tag + */ + this._readTagForward = function(iterator) { + var token = iterator.getCurrentToken(); + if (!token) + return null; + + var value = ""; + var start; + + do { + if (token.type.indexOf("meta.tag") === 0) { + if (!start) { + var start = { + row: iterator.getCurrentTokenRow(), + column: iterator.getCurrentTokenColumn() + }; + } + value += token.value; + if (value.indexOf(">") !== -1) { + var tag = this._parseTag(value); + tag.start = start; + tag.end = { + row: iterator.getCurrentTokenRow(), + column: iterator.getCurrentTokenColumn() + token.value.length + }; + iterator.stepForward(); + return tag; + } + } + } while(token = iterator.stepForward()); + + return null; + }; + + this._readTagBackward = function(iterator) { + var token = iterator.getCurrentToken(); + if (!token) + return null; + + var value = ""; + var end; + + do { + if (token.type.indexOf("meta.tag") === 0) { + if (!end) { + end = { + row: iterator.getCurrentTokenRow(), + column: iterator.getCurrentTokenColumn() + token.value.length + }; + } + value = token.value + value; + if (value.indexOf("<") !== -1) { + var tag = this._parseTag(value); + tag.end = end; + tag.start = { + row: iterator.getCurrentTokenRow(), + column: iterator.getCurrentTokenColumn() + }; + iterator.stepBackward(); + return tag; + } + } + } while(token = iterator.stepBackward()); + + return null; + }; + + this._pop = function(stack, tag) { + while (stack.length) { + + var top = stack[stack.length-1]; + if (!tag || top.tagName == tag.tagName) { + return stack.pop(); + } + else if (this.voidElements[tag.tagName]) { + return; + } + else if (this.voidElements[top.tagName]) { + stack.pop(); + continue; + } else { + return null; + } + } + }; + + this.getFoldWidgetRange = function(session, foldStyle, row) { + var firstTag = this._getFirstTagInLine(session, row); + + if (!firstTag.match) + return null; + + var isBackward = firstTag.closing || firstTag.selfClosing; + var stack = []; + var tag; + + if (!isBackward) { + var iterator = new TokenIterator(session, row, firstTag.column); + var start = { + row: row, + column: firstTag.column + firstTag.tagName.length + 2 + }; + while (tag = this._readTagForward(iterator)) { + if (tag.selfClosing) { + if (!stack.length) { + tag.start.column += tag.tagName.length + 2; + tag.end.column -= 2; + return Range.fromPoints(tag.start, tag.end); + } else + continue; + } + + if (tag.closing) { + this._pop(stack, tag); + if (stack.length == 0) + break; + } + else { + stack.push(tag) + } + } + return Range.fromPoints(start, tag.start); + } + else { + var iterator = new TokenIterator(session, row, firstTag.column + firstTag.match.length); + var end = { + row: row, + column: firstTag.column + }; + + while (tag = this._readTagBackward(iterator)) { + if (tag.selfClosing) { + if (!stack.length) { + tag.start.column += tag.tagName.length + 2; + tag.end.column -= 2; + return Range.fromPoints(tag.start, tag.end); + } else + continue; + } + + if (!tag.closing) { + this._pop(stack, tag); + if (stack.length == 0) + break; + } + else { + stack.push(tag) + } + } + tag.start.column += tag.tagName.length + 2; + return Range.fromPoints(tag.start, end); + } + + }; + }).call(FoldMode.prototype); }); \ No newline at end of file diff --git a/lib/ace/mode/folding/xml_test.js b/lib/ace/mode/folding/xml_test.js index 7af1c201..2a47dfc5 100644 --- a/lib/ace/mode/folding/xml_test.js +++ b/lib/ace/mode/folding/xml_test.js @@ -80,8 +80,8 @@ module.exports = { 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); + assert.range(session.getFoldWidgetRange(0), 0, 8, 2, 0); + assert.range(session.getFoldWidgetRange(2), 0, 8, 2, 0); }, "test: fold should skip multi line self closing elements": function() { @@ -103,10 +103,10 @@ module.exports = { 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(0), 0, 8, 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); + assert.range(session.getFoldWidgetRange(4), 0, 8, 4, 0); } }; diff --git a/lib/ace/mode/html_tokenizer_test.js b/lib/ace/mode/html_tokenizer_test.js index ec96dc49..06640b38 100644 --- a/lib/ace/mode/html_tokenizer_test.js +++ b/lib/ace/mode/html_tokenizer_test.js @@ -53,16 +53,19 @@ module.exports = { var line = "'123'"; var tokens = this.tokenizer.getLineTokens(line, "start").tokens; - assert.equal(9, tokens.length); + assert.equal(12, tokens.length); assert.equal("meta.tag", tokens[0].type); - assert.equal("text", tokens[1].type); - assert.equal("entity.other.attribute-name", tokens[2].type); - assert.equal("keyword.operator", tokens[3].type); - assert.equal("string", tokens[4].type); - assert.equal("meta.tag", tokens[5].type); - assert.equal("keyword.definition", tokens[6].type); - assert.equal("meta.tag", tokens[7].type); - assert.equal("text", tokens[8].type); + assert.equal("meta.tag.script", tokens[1].type); + assert.equal("text", tokens[2].type); + assert.equal("entity.other.attribute-name", tokens[3].type); + assert.equal("keyword.operator", tokens[4].type); + assert.equal("string", tokens[5].type); + assert.equal("meta.tag", tokens[6].type); + assert.equal("keyword.definition", tokens[7].type); + assert.equal("meta.tag", tokens[8].type); + assert.equal("meta.tag.script", tokens[9].type); + assert.equal("meta.tag", tokens[10].type); + assert.equal("text", tokens[11].type); }, "test: tokenize multiline attribute value with double quotes": function() {