Files
atom/vendor/ace/edit_session/folding.js
2011-08-27 03:55:56 -07:00

496 lines
17 KiB
JavaScript

/* vim:ts=4:sts=4:sw=4:
* ***** 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):
* Julian Viereck <julian DOT viereck AT gmail DOT com>
*
* 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 ***** */
define(function(require, exports, module) {
var Range = require("ace/range").Range;
var FoldLine = require("ace/edit_session/fold_line").FoldLine;
var Fold = require("ace/edit_session/fold").Fold;
function Folding() {
/**
* Looks up a fold at a given row/column. Possible values for side:
* -1: ignore a fold if fold.start = row/column
* +1: ignore a fold if fold.end = row/column
*/
this.getFoldAt = function(row, column, side) {
var foldLine = this.getFoldLine(row);
if (!foldLine)
return null;
var folds = foldLine.folds;
for (var i = 0; i < folds.length; i++) {
var fold = folds[i];
if (fold.range.contains(row, column)) {
if (side == 1 && fold.range.isEnd(row, column)) {
continue;
} else if (side == -1 && fold.range.isStart(row, column)) {
continue;
}
return fold;
}
}
};
/**
* Returns all folds in the given range. Note, that this will return folds
*
*/
this.getFoldsInRange = function(range) {
range = range.clone();
var start = range.start;
var end = range.end;
var foldLines = this.$foldData;
var foundFolds = [];
start.column += 1;
end.column -= 1;
for (var i = 0; i < foldLines.length; i++) {
var cmp = foldLines[i].range.compareRange(range);
if (cmp == 2) {
// Range is before foldLine. No intersection. This means,
// there might be other foldLines that intersect.
continue;
}
else if (cmp == -2) {
// Range is after foldLine. There can't be any other foldLines then,
// so let's give up.
break;
}
var folds = foldLines[i].folds;
for (var j = 0; j < folds.length; j++) {
var fold = folds[j];
cmp = fold.range.compareRange(range);
if (cmp == -2) {
break;
} else if (cmp == 2) {
continue;
} else
// WTF-state: Can happen due to -1/+1 to start/end column.
if (cmp == 42) {
break;
}
foundFolds.push(fold);
}
}
return foundFolds;
}
/**
* Returns the string between folds at the given position.
* E.g.
* foo<fold>b|ar<fold>wolrd -> "bar"
* foo<fold>bar<fold>wol|rd -> "world"
* foo<fold>bar<fo|ld>wolrd -> <null>
*
* where | means the position of row/column
*
* The trim option determs if the return string should be trimed according
* to the "side" passed with the trim value:
*
* E.g.
* foo<fold>b|ar<fold>wolrd -trim=-1> "b"
* foo<fold>bar<fold>wol|rd -trim=+1> "rld"
* fo|o<fold>bar<fold>wolrd -trim=00> "foo"
*/
this.getFoldStringAt = function(row, column, trim, foldLine) {
var foldLine = foldLine || this.getFoldLine(row);
if (!foldLine)
return null;
var lastFold = {
end: { column: 0 }
};
// TODO: Refactor to use getNextFoldTo function.
for (var i = 0; i < foldLine.folds.length; i++) {
var fold = foldLine.folds[i];
var cmp = fold.range.compareEnd(row, column);
if (cmp == -1) {
var str = this
.getLine(fold.start.row)
.substring(lastFold.end.column, fold.start.column);
break;
}
else if (cmp == 0) {
return null;
}
lastFold = fold;
}
if (!str)
str = this.getLine(fold.start.row).substring(lastFold.end.column);
if (trim == -1)
return str.substring(0, column - lastFold.end.column);
else if (trim == 1)
return str.substring(column - lastFold.end.column)
else
return str;
}
this.getFoldLine = function(docRow, startFoldLine) {
var foldData = this.$foldData;
var i = 0;
if (startFoldLine)
i = foldData.indexOf(startFoldLine);
if (i == -1)
i = 0;
for (i; i < foldData.length; i++) {
var foldLine = foldData[i];
if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) {
return foldLine;
} else if (foldLine.end.row > docRow) {
return null;
}
}
return null;
}
// returns the fold which starts after or contains docRow
this.getNextFold = function(docRow, startFoldLine) {
var foldData = this.$foldData, ans;
var i = 0;
if (startFoldLine)
i = foldData.indexOf(startFoldLine);
if (i == -1)
i = 0;
for (i; i < foldData.length; i++) {
var foldLine = foldData[i];
if (foldLine.end.row >= docRow) {
return foldLine;
}
}
return null;
}
this.getFoldedRowCount = function(first, last) {
var foldData = this.$foldData, rowCount = last-first+1;
for (var i = 0; i < foldData.length; i++) {
var foldLine = foldData[i],
end = foldLine.end.row,
start = foldLine.start.row;
if (end >= last) {
if(start < last) {
if(start >= first)
rowCount -= last-start;
else
rowCount = 0;//in one fold
}
break;
} else if(end >= first){
if (start >= first) //fold inside range
rowCount -= end-start;
else
rowCount -= end-first+1;
}
}
return rowCount;
}
this.$addFoldLine = function(foldLine) {
this.$foldData.push(foldLine);
this.$foldData.sort(function(a, b) {
return a.start.row - b.start.row;
});
return foldLine;
}
/**
* Adds a new fold.
*
* @returns
* The new created Fold object or an existing fold object in case the
* passed in range fits an existing fold exactly.
*/
this.addFold = function(placeholder, range) {
var foldData = this.$foldData;
var added = false;
if (placeholder instanceof Fold)
var fold = placeholder;
else
fold = new Fold(range, placeholder);
var startRow = fold.start.row;
var startColumn = fold.start.column;
var endRow = fold.end.row;
var endColumn = fold.end.column;
// --- Some checking ---
if (fold.placeholder.length < 2)
throw "Placeholder has to be at least 2 characters";
if (startRow == endRow && endColumn - startColumn < 2)
throw "The range has to be at least 2 characters width";
var existingFold = this.getFoldAt(startRow, startColumn, 1);
if (
existingFold
&& existingFold.range.isEnd(endRow, endColumn)
&& existingFold.range.isStart(startRow, startColumn)
) {
return fold;
}
existingFold = this.getFoldAt(startRow, startColumn, 1);
if (existingFold && !existingFold.range.isStart(startRow, startColumn))
throw "A fold can't start inside of an already existing fold";
existingFold = this.getFoldAt(endRow, endColumn, -1);
if (existingFold && !existingFold.range.isEnd(endRow, endColumn))
throw "A fold can't end inside of an already existing fold";
if (endRow >= this.doc.getLength())
throw "End of fold is outside of the document.";
if (endColumn > this.getLine(endRow).length || startColumn > this.getLine(startRow).length)
throw "End of fold is outside of the document.";
// Check if there are folds in the range we create the new fold for.
var folds = this.getFoldsInRange(fold.range);
if (folds.length > 0) {
// Remove the folds from fold data.
this.removeFolds(folds);
// Add the removed folds as subfolds on the new fold.
fold.subFolds = folds;
}
for (var i = 0; i < foldData.length; i++) {
var foldLine = foldData[i];
if (endRow == foldLine.start.row) {
foldLine.addFold(fold);
added = true;
break;
}
else if (startRow == foldLine.end.row) {
foldLine.addFold(fold);
added = true;
if (!fold.sameRow) {
// Check if we might have to merge two FoldLines.
foldLineNext = foldData[i + 1];
if (foldLineNext && foldLineNext.start.row == endRow) {
// We need to merge!
foldLine.merge(foldLineNext);
break;
}
}
break;
}
else if (endRow <= foldLine.start.row) {
break;
}
}
if (!added)
foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold));
if (this.$useWrapMode)
this.$updateWrapData(foldLine.start.row, foldLine.start.row);
// Notify that fold data has changed.
this.$modified = true;
this._dispatchEvent("changeFold", { data: fold });
return fold;
};
this.addFolds = function(folds) {
folds.forEach(function(fold) {
this.addFold(fold);
}, this);
};
this.removeFold = function(fold) {
var foldLine = fold.foldLine;
var startRow = foldLine.start.row;
var endRow = foldLine.end.row;
var foldLines = this.$foldData,
folds = foldLine.folds;
// Simple case where there is only one fold in the FoldLine such that
// the entire fold line can get removed directly.
if (folds.length == 1) {
foldLines.splice(foldLines.indexOf(foldLine), 1);
} else
// If the fold is the last fold of the foldLine, just remove it.
if (foldLine.range.isEnd(fold.end.row, fold.end.column)) {
folds.pop();
foldLine.end.row = folds[folds.length - 1].end.row;
foldLine.end.column = folds[folds.length - 1].end.column;
} else
// If the fold is the first fold of the foldLine, just remove it.
if (foldLine.range.isStart(fold.start.row, fold.start.column)) {
folds.shift();
foldLine.start.row = folds[0].start.row;
foldLine.start.column = folds[0].start.column;
} else
// We know there are more then 2 folds and the fold is not at the edge.
// This means, the fold is somewhere in between.
//
// If the fold is in one row, we just can remove it.
if (fold.sameRow) {
folds.splice(folds.indexOf(fold), 1);
} else
// The fold goes over more then one row. This means remvoing this fold
// will cause the fold line to get splitted up.
{
var newFoldLine = foldLine.split(fold.start.row, fold.start.column);
newFoldLine.folds.shift();
foldLine.start.row = folds[0].start.row;
foldLine.start.column = folds[0].start.column;
this.$addFoldLine(newFoldLine);
}
if (this.$useWrapMode) {
this.$updateWrapData(startRow, endRow);
}
// Notify that fold data has changed.
this.$modified = true;
this._dispatchEvent("changeFold", { data: fold });
}
this.removeFolds = function(folds) {
// We need to clone the folds array passed in as it might be the folds
// array of a fold line and as we call this.removeFold(fold), folds
// are removed from folds and changes the current index.
var cloneFolds = [];
for (var i = 0; i < folds.length; i++) {
cloneFolds.push(folds[i]);
}
cloneFolds.forEach(function(fold) {
this.removeFold(fold);
}, this);
this.$modified = true;
}
this.expandFold = function(fold) {
this.removeFold(fold);
fold.subFolds.forEach(function(fold) {
this.addFold(fold);
}, this);
fold.subFolds = [];
}
this.expandFolds = function(folds) {
folds.forEach(function(fold) {
this.expandFold(fold);
}, this);
}
/**
* Checks if a given documentRow is folded. This is true if there are some
* folded parts such that some parts of the line is still visible.
**/
this.isRowFolded = function(docRow, startFoldRow) {
return !!this.getFoldLine(docRow, startFoldRow);
};
this.getRowFoldEnd = function(docRow, startFoldRow) {
var foldLine = this.getFoldLine(docRow, startFoldRow);
return (foldLine
? foldLine.end.row
: docRow)
};
this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) {
if (startRow == null) {
startRow = foldLine.start.row;
startColumn = 0;
}
if (endRow == null) {
endRow = foldLine.end.row;
endColumn = this.getLine(endRow).length;
}
// Build the textline using the FoldLine walker.
var line = "";
var doc = this.doc;
var textLine = "";
foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
if (row < startRow) {
return;
} else if (row == startRow) {
if (column < startColumn) {
return;
}
lastColumn = Math.max(startColumn, lastColumn);
}
if (placeholder) {
textLine += placeholder;
} else {
textLine += doc.getLine(row).substring(lastColumn, column);
}
}.bind(this), endRow, endColumn);
return textLine;
};
this.getDisplayLine = function(row, endColumn, startRow, startColumn) {
var foldLine = this.getFoldLine(row);
if (!foldLine) {
var line;
line = this.doc.getLine(row);
return line.substring(startColumn || 0, endColumn || line.length);
} else {
return this.getFoldDisplayLine(
foldLine, row, endColumn, startRow, startColumn);
}
};
this.$cloneFoldData = function() {
var foldData = this.$foldData;
var fd = [];
fd = this.$foldData.map(function(foldLine) {
var folds = foldLine.folds.map(function(fold) {
return fold.clone();
});
return new FoldLine(fd, folds);
});
return fd;
};
}
exports.Folding = Folding;
});