fix folding after html mode changes

This commit is contained in:
Fabian Jakobs 2011-12-15 17:23:34 +01:00
commit 25400848f2
4 changed files with 218 additions and 124 deletions

View file

@ -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);
}
};

View file

@ -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*)(<?(\/?)([-_a-zA-Z0-9:!]*)\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);
});

View file

@ -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);
}
};

View file

@ -53,16 +53,19 @@ module.exports = {
var line = "<script a='a'>var</script>'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() {