mirror of
https://github.com/atom/atom.git
synced 2026-02-12 15:45:23 -05:00
496 lines
17 KiB
JavaScript
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;
|
|
|
|
}); |