287 lines
10 KiB
JavaScript
287 lines
10 KiB
JavaScript
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Distributed under the BSD license:
|
||
|
*
|
||
|
* Copyright (c) 2010, Ajax.org B.V.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* * Neither the name of Ajax.org B.V. nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
define(function(require, exports, module) {
|
||
|
"use strict";
|
||
|
|
||
|
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;
|
||
|
|
||
|
var FoldMode = exports.FoldMode = function(voidElements, optionalEndTags) {
|
||
|
BaseFoldMode.call(this);
|
||
|
this.voidElements = voidElements || {};
|
||
|
this.optionalEndTags = oop.mixin({}, this.voidElements);
|
||
|
if (optionalEndTags)
|
||
|
oop.mixin(this.optionalEndTags, optionalEndTags);
|
||
|
|
||
|
};
|
||
|
oop.inherits(FoldMode, BaseFoldMode);
|
||
|
|
||
|
var Tag = function() {
|
||
|
this.tagName = "";
|
||
|
this.closing = false;
|
||
|
this.selfClosing = false;
|
||
|
this.start = {row: 0, column: 0};
|
||
|
this.end = {row: 0, column: 0};
|
||
|
};
|
||
|
|
||
|
function is(token, type) {
|
||
|
return token.type.lastIndexOf(type + ".xml") > -1;
|
||
|
}
|
||
|
|
||
|
(function() {
|
||
|
|
||
|
this.getFoldWidget = function(session, foldStyle, row) {
|
||
|
var tag = this._getFirstTagInLine(session, row);
|
||
|
|
||
|
if (!tag)
|
||
|
return this.getCommentFoldWidget(session, row);
|
||
|
|
||
|
if (tag.closing || (!tag.tagName && tag.selfClosing))
|
||
|
return foldStyle == "markbeginend" ? "end" : "";
|
||
|
|
||
|
if (!tag.tagName || tag.selfClosing || this.voidElements.hasOwnProperty(tag.tagName.toLowerCase()))
|
||
|
return "";
|
||
|
|
||
|
if (this._findEndTagInLine(session, row, tag.tagName, tag.end.column))
|
||
|
return "";
|
||
|
|
||
|
return "start";
|
||
|
};
|
||
|
|
||
|
this.getCommentFoldWidget = function(session, row) {
|
||
|
if (/comment/.test(session.getState(row)) && /<!-/.test(session.getLine(row)))
|
||
|
return "start";
|
||
|
return "";
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* returns a first tag (or a fragment) in a line
|
||
|
*/
|
||
|
this._getFirstTagInLine = function(session, row) {
|
||
|
var tokens = session.getTokens(row);
|
||
|
var tag = new Tag();
|
||
|
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var token = tokens[i];
|
||
|
if (is(token, "tag-open")) {
|
||
|
tag.end.column = tag.start.column + token.value.length;
|
||
|
tag.closing = is(token, "end-tag-open");
|
||
|
token = tokens[++i];
|
||
|
if (!token)
|
||
|
return null;
|
||
|
tag.tagName = token.value;
|
||
|
tag.end.column += token.value.length;
|
||
|
for (i++; i < tokens.length; i++) {
|
||
|
token = tokens[i];
|
||
|
tag.end.column += token.value.length;
|
||
|
if (is(token, "tag-close")) {
|
||
|
tag.selfClosing = token.value == '/>';
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return tag;
|
||
|
} else if (is(token, "tag-close")) {
|
||
|
tag.selfClosing = token.value == '/>';
|
||
|
return tag;
|
||
|
}
|
||
|
tag.start.column += token.value.length;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
this._findEndTagInLine = function(session, row, tagName, startColumn) {
|
||
|
var tokens = session.getTokens(row);
|
||
|
var column = 0;
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var token = tokens[i];
|
||
|
column += token.value.length;
|
||
|
if (column < startColumn)
|
||
|
continue;
|
||
|
if (is(token, "end-tag-open")) {
|
||
|
token = tokens[i + 1];
|
||
|
if (token && token.value == tagName)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* reads a full tag and places the iterator after the tag
|
||
|
*/
|
||
|
this._readTagForward = function(iterator) {
|
||
|
var token = iterator.getCurrentToken();
|
||
|
if (!token)
|
||
|
return null;
|
||
|
|
||
|
var tag = new Tag();
|
||
|
do {
|
||
|
if (is(token, "tag-open")) {
|
||
|
tag.closing = is(token, "end-tag-open");
|
||
|
tag.start.row = iterator.getCurrentTokenRow();
|
||
|
tag.start.column = iterator.getCurrentTokenColumn();
|
||
|
} else if (is(token, "tag-name")) {
|
||
|
tag.tagName = token.value;
|
||
|
} else if (is(token, "tag-close")) {
|
||
|
tag.selfClosing = token.value == "/>";
|
||
|
tag.end.row = iterator.getCurrentTokenRow();
|
||
|
tag.end.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 tag = new Tag();
|
||
|
do {
|
||
|
if (is(token, "tag-open")) {
|
||
|
tag.closing = is(token, "end-tag-open");
|
||
|
tag.start.row = iterator.getCurrentTokenRow();
|
||
|
tag.start.column = iterator.getCurrentTokenColumn();
|
||
|
iterator.stepBackward();
|
||
|
return tag;
|
||
|
} else if (is(token, "tag-name")) {
|
||
|
tag.tagName = token.value;
|
||
|
} else if (is(token, "tag-close")) {
|
||
|
tag.selfClosing = token.value == "/>";
|
||
|
tag.end.row = iterator.getCurrentTokenRow();
|
||
|
tag.end.column = iterator.getCurrentTokenColumn() + token.value.length;
|
||
|
}
|
||
|
} 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.optionalEndTags.hasOwnProperty(top.tagName)) {
|
||
|
stack.pop();
|
||
|
continue;
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.getFoldWidgetRange = function(session, foldStyle, row) {
|
||
|
var firstTag = this._getFirstTagInLine(session, row);
|
||
|
|
||
|
if (!firstTag) {
|
||
|
return this.getCommentFoldWidget(session, row)
|
||
|
&& session.getCommentFoldRange(row, session.getLine(row).length);
|
||
|
}
|
||
|
|
||
|
var isBackward = firstTag.closing || firstTag.selfClosing;
|
||
|
var stack = [];
|
||
|
var tag;
|
||
|
|
||
|
if (!isBackward) {
|
||
|
var iterator = new TokenIterator(session, row, firstTag.start.column);
|
||
|
var start = {
|
||
|
row: row,
|
||
|
column: firstTag.start.column + firstTag.tagName.length + 2
|
||
|
};
|
||
|
if (firstTag.start.row == firstTag.end.row)
|
||
|
start.column = firstTag.end.column;
|
||
|
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)
|
||
|
return Range.fromPoints(start, tag.start);
|
||
|
}
|
||
|
else {
|
||
|
stack.push(tag);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
var iterator = new TokenIterator(session, row, firstTag.end.column);
|
||
|
var end = {
|
||
|
row: row,
|
||
|
column: firstTag.start.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) {
|
||
|
tag.start.column += tag.tagName.length + 2;
|
||
|
if (tag.start.row == tag.end.row && tag.start.column < tag.end.column)
|
||
|
tag.start.column = tag.end.column;
|
||
|
return Range.fromPoints(tag.start, end);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
stack.push(tag);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
}).call(FoldMode.prototype);
|
||
|
|
||
|
});
|