First iteration of code folding - not much working yet.

This commit is contained in:
Julian Viereck 2011-04-23 02:34:15 +02:00
commit c010348eae
6 changed files with 198 additions and 55 deletions

View file

@ -147,6 +147,18 @@ exports.launch = function(env) {
var container = document.getElementById("editor"); var container = document.getElementById("editor");
env.editor = new Editor(new Renderer(container, theme)); env.editor = new Editor(new Renderer(container, theme));
// BEGING TESTING
var Range = require("ace/range").Range;
docs.js.addFold(new Range(1,0, 2, 999), "foo...");
window.s = docs.js;
window.e = env.editor;
setTimeout(function() {
env.editor.selection.addEventListener("changeCursor", function() {
console.log(env.editor.selection.getRange() + "");
})
}, 500)
// END TESTING
var modes = { var modes = {
text: new TextMode(), text: new TextMode(),
textile: new TextileMode(), textile: new TextileMode(),

View file

@ -67,6 +67,12 @@ var EditSession = function(text, mode) {
this.setMode(mode); this.setMode(mode);
else else
this.setMode(new TextMode()); this.setMode(new TextMode());
// Set the initial foldData content.
var foldData = this.$foldData = [];
for (var row = 0; row < this.doc.$lines.length; row++) {
foldData.push(false);
}
}; };
@ -94,8 +100,7 @@ var EditSession = function(text, mode) {
this.$informUndoManager.schedule(); this.$informUndoManager.schedule();
} }
this.$updateWrapDataOnChange(e); this.$updateInternalDataOnChange(e);
this.bgTokenizer.start(delta.range.start.row); this.bgTokenizer.start(delta.range.start.row);
this._dispatchEvent("change", e); this._dispatchEvent("change", e);
}; };
@ -848,11 +853,9 @@ var EditSession = function(text, mode) {
}; };
}; };
this.$updateWrapDataOnChange = function(e) { // TODO: Really want to keep this name?
if (!this.$useWrapMode) { this.$updateInternalDataOnChange = function(e) {
return; var useWrapMode = this.$useWrapMode;
}
var len; var len;
var action = e.data.action; var action = e.data.action;
var firstRow = e.data.range.start.row, var firstRow = e.data.range.start.row,
@ -871,20 +874,35 @@ var EditSession = function(text, mode) {
if (len != 0) { if (len != 0) {
if (action.indexOf("remove") != -1) { if (action.indexOf("remove") != -1) {
this.$wrapData.splice(firstRow, len); useWrapMode && this.$wrapData.splice(firstRow, len);
// TODO: More checking needed here.
this.$foldData.splice(firstRow, len);
lastRow = firstRow; lastRow = firstRow;
} else { } else {
var args = [firstRow, 0]; var args;
for (var i = 0; i < len; i++) args.push([]); if (useWrapMode) {
this.$wrapData.splice.apply(this.$wrapData, args); args = [firstRow, 0];
for (var i = 0; i < len; i++) args.push([]);
this.$wrapData.splice.apply(this.$wrapData, args);
}
args = [firstRow, 0];
for (var i = 0; i < len; i++) args.push(false);
// TODO: More checking needed here.
this.$foldData.splice.apply(this.$foldData, args);
} }
} }
if (this.$wrapData.length != this.doc.$lines.length) { if (useWrapMode && this.$wrapData.length != this.doc.$lines.length) {
console.error("The length of doc.$lines and $wrapData have to be the same!"); console.error("The length of doc.$lines and $wrapData have to be the same!");
} }
if (this.$foldData.length != this.doc.$lines.length) {
console.error("The length of doc.$lines and $foldData have to be the same!");
}
this.$updateWrapData(firstRow, lastRow); // TODO:
// this.$updateFoldData(firstRow, lastRow);
useWrapMode && this.$updateWrapData(firstRow, lastRow);
}; };
this.$updateWrapData = function(firstRow, lastRow) { this.$updateWrapData = function(firstRow, lastRow) {
@ -1350,8 +1368,84 @@ var EditSession = function(text, mode) {
c >= 0xFE68 && c <= 0xFE6B || c >= 0xFE68 && c <= 0xFE6B ||
c >= 0xFF01 && c <= 0xFF60 || c >= 0xFF01 && c <= 0xFF60 ||
c >= 0xFFE0 && c <= 0xFFE6; c >= 0xFFE0 && c <= 0xFFE6;
};
// == Folding Code ========================================================
/**
* Simple fold-data struct.
**/
function Fold(range, placeholder) {
this.placeholder = placeholder;
this.range = range;
this.start = range.start;
this.end = range.end;
} }
/**
* Adds a new fold.
*/
this.addFold = function(range, placeholder) {
var startRow = range.start.row,
endRow = range.end.row,
foldData = this.$foldData;
// In case there is no fold data for the start row yet.
if (!Array.isArray(foldData[startRow])) {
foldData[startRow] = [];
}
var fold = new Fold(range, placeholder);
foldData[startRow].push(fold);
// Mark all lines folded by this fold as folded.
for (var row = startRow + 1; row <= endRow; row++) {
foldData[row] = fold;
}
// TODO: Recalculate wrapData
// TODO: Recalculate width etc.
// TODO: Mark as dirty etc.
// Notify that fold data has changed.
this._dispatchEvent("changeFold");
};
/**
* 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) {
return this.$foldData[docRow];
};
this.getRowLastFold = function(docRow) {
var fold = this.$foldData[docRow];
if (!fold) {
return false;
} else if (Array.isArray(fold)) {
return fold[fold.length - 1];
} else {
return fold;
}
};
this.getRowFoldEnd = function(docRow) {
return (this.$foldData[docRow]
? this.getRowLastFold(docRow).end.row
: docRow);
};
/**
* Checks if a given documentRow is visible or not. Not beeing visible means
* it's folded completly.
**/
this.isRowVisible = function(docRow) {
var fold = this.$foldData[docRow];
return !fold || Array.isArray(fold);
};
}).call(EditSession.prototype); }).call(EditSession.prototype);
exports.EditSession = EditSession; exports.EditSession = EditSession;

View file

@ -125,6 +125,7 @@ var Editor =function(renderer, session) {
this.session.removeEventListener("changeTabSize", this.$onChangeTabSize); this.session.removeEventListener("changeTabSize", this.$onChangeTabSize);
this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit); this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit);
this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode); this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode);
this.session.removeEventListener("onChangeFold", this.$onChangeFold);
this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker);
this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker); this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker);
this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint); this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint);
@ -159,6 +160,9 @@ var Editor =function(renderer, session) {
this.$onChangeWrapMode = this.onChangeWrapMode.bind(this); this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
session.addEventListener("changeWrapMode", this.$onChangeWrapMode); session.addEventListener("changeWrapMode", this.$onChangeWrapMode);
this.$onChangeFold = this.onChangeFold.bind(this);
session.addEventListener("changeFold", this.$onChangeFold);
this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this); this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker);
@ -372,6 +376,11 @@ var Editor =function(renderer, session) {
this.renderer.onResize(true); this.renderer.onResize(true);
}; };
this.onChangeFold = function() {
// TODO: This might be too much updating. Okay for now.
this.render.updateFull();
};
this.getCopyText = function() { this.getCopyText = function() {
if (!this.selection.isEmpty()) { if (!this.selection.isEmpty()) {
return this.session.getTextRange(this.getSelectionRange()); return this.session.getTextRange(this.getSelectionRange());
@ -420,8 +429,8 @@ var Editor =function(renderer, session) {
var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString()); var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
var end = session.insert(cursor, text); var end = session.insert(cursor, text);
var lineState = session.getState(cursor.row); var lineState = session.getState(cursor.row);
// TODO disabled multiline auto indent // TODO disabled multiline auto indent
// possibly doing the indent before inserting the text // possibly doing the indent before inserting the text
// if (cursor.row !== end.row) { // if (cursor.row !== end.row) {

View file

@ -111,6 +111,9 @@ var Gutter = function(parentEl) {
annotation.className, annotation.className,
"' title='", annotation.text.join("\n"), "' title='", annotation.text.join("\n"),
"' style='height:", this.session.getRowHeight(config, i), "px;'>", (i+1), "</div>"); "' style='height:", this.session.getRowHeight(config, i), "px;'>", (i+1), "</div>");
i = this.session.getRowFoldEnd(i);
} }
this.element = dom.setInnerHtml(this.element, html.join("")); this.element = dom.setInnerHtml(this.element, html.join(""));
this.element.style.height = config.minHeight + "px"; this.element.style.height = config.minHeight + "px";

View file

@ -186,18 +186,30 @@ var Text = function(parentEl) {
var first = Math.max(firstRow, config.firstRow); var first = Math.max(firstRow, config.firstRow);
var last = Math.min(lastRow, config.lastRow); var last = Math.min(lastRow, config.lastRow);
var lineElements = this.element.childNodes; var lineElements = this.element.childNodes,
var tokens = this.session.getTokens(first, last); lineElementsIdx = 0;
for (var row = config.firstRow; row < first; row++) {
lineElementsIdx ++;
if (this.session.isRowFolded(row)) {
var fold = this.session.getRowLastFold(row);
row = fold.end.row;
}
}
for (var i=first; i<=last; i++) { for (var i=first; i<=last; i++) {
var lineElement = lineElements[i - config.firstRow]; var lineElement = lineElements[lineElementsIdx++];
if (!lineElement) if (!lineElement)
continue; continue;
var html = []; var html = [];
this.$renderLine(html, i, tokens[i-first].tokens); var tokens = this.session.getTokens(i, i);
this.$renderLine(html, i, tokens[0].tokens);
lineElement = dom.setInnerHtml(lineElement, html.join("")); lineElement = dom.setInnerHtml(lineElement, html.join(""));
lineElement.style.height = lineElement.style.height =
this.session.getRowHeight(config, i) + "px"; this.session.getRowHeight(config, i) + "px";
i = this.session.getRowFoldEnd(i);
} }
}; };
@ -238,17 +250,23 @@ var Text = function(parentEl) {
this.$renderLinesFragment = function(config, firstRow, lastRow) { this.$renderLinesFragment = function(config, firstRow, lastRow) {
var fragment = document.createDocumentFragment(); var fragment = document.createDocumentFragment();
var tokens = this.session.getTokens(firstRow, lastRow);
for (var row=firstRow; row<=lastRow; row++) { for (var row=firstRow; row<=lastRow; row++) {
var lineEl = dom.createElement("div"); var lineEl = dom.createElement("div");
lineEl.className = "ace_line"; lineEl.className = "ace_line";
var style = lineEl.style; var style = lineEl.style;
style.height = this.session.getRowHeight(config, row) + "px"; style.height = this.session.getRowHeight(config, row) + "px";
style.width = config.width + "px"; style.width = config.width + "px";
var html = []; var html = [];
if (tokens.length > row-firstRow) // Get the tokens per line as there might be some lines in between
this.$renderLine(html, row, tokens[row-firstRow].tokens); // beeing folded.
// OPTIMIZE: If there is a long block of unfolded lines, just make
// this call once for that big block of unfolded lines.
var tokens = this.session.getTokens(row, row);
if (tokens.length == 1)
this.$renderLine(html, row, tokens[0].tokens);
// don't use setInnerHtml since we are working with an empty DIV // don't use setInnerHtml since we are working with an empty DIV
lineEl.innerHTML = html.join(""); lineEl.innerHTML = html.join("");
fragment.appendChild(lineEl); fragment.appendChild(lineEl);
@ -276,6 +294,13 @@ var Text = function(parentEl) {
}; };
this.$renderLine = function(stringBuilder, row, tokens) { this.$renderLine = function(stringBuilder, row, tokens) {
// Nothing to do if the entire line is folded.
// TODO: Remove this, once the folding feature is done. Only for
// developing stuff at the moment.
if (!this.session.isRowVisible(row)) {
throw "Calling renderLine on folded line doesn't make sense?";
}
var _self = this, var _self = this,
characterWidth = this.config.characterWidth, characterWidth = this.config.characterWidth,
screenColumn = 0; screenColumn = 0;