First iteration of code folding - not much working yet.
This commit is contained in:
parent
298408b93a
commit
c010348eae
6 changed files with 198 additions and 55 deletions
12
demo/demo.js
12
demo/demo.js
|
|
@ -147,6 +147,18 @@ exports.launch = function(env) {
|
|||
var container = document.getElementById("editor");
|
||||
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 = {
|
||||
text: new TextMode(),
|
||||
textile: new TextileMode(),
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ var EditSession = function(text, mode) {
|
|||
this.setMode(mode);
|
||||
else
|
||||
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.$updateWrapDataOnChange(e);
|
||||
|
||||
this.$updateInternalDataOnChange(e);
|
||||
this.bgTokenizer.start(delta.range.start.row);
|
||||
this._dispatchEvent("change", e);
|
||||
};
|
||||
|
|
@ -118,7 +123,7 @@ var EditSession = function(text, mode) {
|
|||
this.getState = function(row) {
|
||||
return this.bgTokenizer.getState(row);
|
||||
};
|
||||
|
||||
|
||||
this.getTokens = function(firstRow, lastRow) {
|
||||
return this.bgTokenizer.getTokens(firstRow, lastRow);
|
||||
};
|
||||
|
|
@ -411,7 +416,7 @@ var EditSession = function(text, mode) {
|
|||
} else {
|
||||
this.bgTokenizer.setTokenizer(tokenizer);
|
||||
}
|
||||
|
||||
|
||||
this.bgTokenizer.setDocument(this.getDocument());
|
||||
this.bgTokenizer.start(0);
|
||||
|
||||
|
|
@ -848,11 +853,9 @@ var EditSession = function(text, mode) {
|
|||
};
|
||||
};
|
||||
|
||||
this.$updateWrapDataOnChange = function(e) {
|
||||
if (!this.$useWrapMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Really want to keep this name?
|
||||
this.$updateInternalDataOnChange = function(e) {
|
||||
var useWrapMode = this.$useWrapMode;
|
||||
var len;
|
||||
var action = e.data.action;
|
||||
var firstRow = e.data.range.start.row,
|
||||
|
|
@ -871,20 +874,35 @@ var EditSession = function(text, mode) {
|
|||
|
||||
if (len != 0) {
|
||||
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;
|
||||
} else {
|
||||
var args = [firstRow, 0];
|
||||
for (var i = 0; i < len; i++) args.push([]);
|
||||
this.$wrapData.splice.apply(this.$wrapData, args);
|
||||
var args;
|
||||
if (useWrapMode) {
|
||||
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!");
|
||||
}
|
||||
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) {
|
||||
|
|
@ -1312,7 +1330,7 @@ var EditSession = function(text, mode) {
|
|||
}
|
||||
return screenRows;
|
||||
}
|
||||
|
||||
|
||||
// For every keystroke this gets called once per char in the whole doc!!
|
||||
// Wouldn't hurt to make it a bit faster for c >= 0x1100
|
||||
function isFullWidth(c) {
|
||||
|
|
@ -1350,8 +1368,84 @@ var EditSession = function(text, mode) {
|
|||
c >= 0xFE68 && c <= 0xFE6B ||
|
||||
c >= 0xFF01 && c <= 0xFF60 ||
|
||||
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);
|
||||
|
||||
exports.EditSession = EditSession;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ var Editor =function(renderer, session) {
|
|||
|
||||
this.textInput = new TextInput(renderer.getTextAreaContainer(), this);
|
||||
this.keyBinding = new KeyBinding(this);
|
||||
|
||||
|
||||
// TODO detect touch event support
|
||||
if (useragent.isIPad) {
|
||||
//this.$mouseHandler = new TouchHandler(this);
|
||||
|
|
@ -125,6 +125,7 @@ var Editor =function(renderer, session) {
|
|||
this.session.removeEventListener("changeTabSize", this.$onChangeTabSize);
|
||||
this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit);
|
||||
this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode);
|
||||
this.session.removeEventListener("onChangeFold", this.$onChangeFold);
|
||||
this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker);
|
||||
this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker);
|
||||
this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint);
|
||||
|
|
@ -159,18 +160,21 @@ var Editor =function(renderer, session) {
|
|||
this.$onChangeWrapMode = this.onChangeWrapMode.bind(this);
|
||||
session.addEventListener("changeWrapMode", this.$onChangeWrapMode);
|
||||
|
||||
this.$onChangeFold = this.onChangeFold.bind(this);
|
||||
session.addEventListener("changeFold", this.$onChangeFold);
|
||||
|
||||
this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this);
|
||||
this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker);
|
||||
|
||||
|
||||
this.$onChangeBackMarker = this.onChangeBackMarker.bind(this);
|
||||
this.session.addEventListener("changeBackMarker", this.$onChangeBackMarker);
|
||||
|
||||
|
||||
this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this);
|
||||
this.session.addEventListener("changeBreakpoint", this.$onChangeBreakpoint);
|
||||
|
||||
this.$onChangeAnnotation = this.onChangeAnnotation.bind(this);
|
||||
this.session.addEventListener("changeAnnotation", this.$onChangeAnnotation);
|
||||
|
||||
|
||||
this.$onCursorChange = this.onCursorChange.bind(this);
|
||||
this.session.addEventListener("changeOverwrite", this.$onCursorChange);
|
||||
|
||||
|
|
@ -251,7 +255,7 @@ var Editor =function(renderer, session) {
|
|||
// to be on the save side we do both
|
||||
// except for IE
|
||||
var _self = this;
|
||||
if (!useragent.isIE) {
|
||||
if (!useragent.isIE) {
|
||||
setTimeout(function() {
|
||||
_self.textInput.focus();
|
||||
});
|
||||
|
|
@ -311,7 +315,7 @@ var Editor =function(renderer, session) {
|
|||
|
||||
this.$updateHighlightActiveLine = function() {
|
||||
var session = this.getSession();
|
||||
|
||||
|
||||
if (session.$highlightLineMarker) {
|
||||
session.removeMarker(session.$highlightLineMarker);
|
||||
}
|
||||
|
|
@ -326,7 +330,7 @@ var Editor =function(renderer, session) {
|
|||
|
||||
this.onSelectionChange = function(e) {
|
||||
var session = this.getSession();
|
||||
|
||||
|
||||
if (session.$selectionMarker) {
|
||||
session.removeMarker(session.$selectionMarker);
|
||||
}
|
||||
|
|
@ -347,11 +351,11 @@ var Editor =function(renderer, session) {
|
|||
this.onChangeFrontMarker = function() {
|
||||
this.renderer.updateFrontMarkers();
|
||||
};
|
||||
|
||||
|
||||
this.onChangeBackMarker = function() {
|
||||
this.renderer.updateBackMarkers();
|
||||
};
|
||||
|
||||
|
||||
this.onChangeBreakpoint = function() {
|
||||
this.renderer.setBreakpoints(this.session.getBreakpoints());
|
||||
};
|
||||
|
|
@ -372,6 +376,11 @@ var Editor =function(renderer, session) {
|
|||
this.renderer.onResize(true);
|
||||
};
|
||||
|
||||
this.onChangeFold = function() {
|
||||
// TODO: This might be too much updating. Okay for now.
|
||||
this.render.updateFull();
|
||||
};
|
||||
|
||||
this.getCopyText = function() {
|
||||
if (!this.selection.isEmpty()) {
|
||||
return this.session.getTextRange(this.getSelectionRange());
|
||||
|
|
@ -397,7 +406,7 @@ var Editor =function(renderer, session) {
|
|||
|
||||
var session = this.session;
|
||||
var mode = session.getMode();
|
||||
|
||||
|
||||
var cursor = this.getCursorPosition();
|
||||
text = text.replace("\t", this.session.getTabString());
|
||||
|
||||
|
|
@ -420,14 +429,14 @@ var Editor =function(renderer, session) {
|
|||
var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString());
|
||||
var end = session.insert(cursor, text);
|
||||
|
||||
|
||||
var lineState = session.getState(cursor.row);
|
||||
|
||||
// TODO disabled multiline auto indent
|
||||
// possibly doing the indent before inserting the text
|
||||
// if (cursor.row !== end.row) {
|
||||
if (session.getDocument().isNewLine(text)) {
|
||||
this.moveCursorTo(cursor.row+1, 0);
|
||||
|
||||
|
||||
var size = session.getTabSize();
|
||||
var minIndent = Number.MAX_VALUE;
|
||||
|
||||
|
|
@ -462,7 +471,7 @@ var Editor =function(renderer, session) {
|
|||
if (shouldOutdent) {
|
||||
mode.autoOutdent(lineState, session, cursor.row);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
this.onTextInput = function(text) {
|
||||
|
|
@ -591,7 +600,7 @@ var Editor =function(renderer, session) {
|
|||
this.session.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
};
|
||||
|
||||
|
||||
this.removeWordRight = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
|
@ -602,7 +611,7 @@ var Editor =function(renderer, session) {
|
|||
this.session.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
};
|
||||
|
||||
|
||||
this.removeWordLeft = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
|
@ -613,7 +622,7 @@ var Editor =function(renderer, session) {
|
|||
this.session.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
};
|
||||
|
||||
|
||||
this.removeToLineStart = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
|
@ -624,7 +633,7 @@ var Editor =function(renderer, session) {
|
|||
this.session.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
};
|
||||
|
||||
|
||||
this.removeToLineEnd = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
|
@ -639,30 +648,30 @@ var Editor =function(renderer, session) {
|
|||
this.splitLine = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
||||
|
||||
if (!this.selection.isEmpty()) {
|
||||
this.session.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
|
||||
var cursor = this.getCursorPosition();
|
||||
this.insert("\n");
|
||||
this.moveCursorToPosition(cursor);
|
||||
};
|
||||
|
||||
|
||||
this.transposeLetters = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
||||
|
||||
if (!this.selection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var cursor = this.getCursorPosition();
|
||||
var column = cursor.column;
|
||||
if (column == 0)
|
||||
return;
|
||||
|
||||
|
||||
var line = this.session.getLine(cursor.row);
|
||||
if (column < line.length) {
|
||||
var swap = line.charAt(column) + line.charAt(column-1);
|
||||
|
|
@ -674,7 +683,7 @@ var Editor =function(renderer, session) {
|
|||
}
|
||||
this.session.replace(range, swap);
|
||||
};
|
||||
|
||||
|
||||
this.indent = function() {
|
||||
if (this.$readOnly)
|
||||
return;
|
||||
|
|
@ -887,7 +896,7 @@ var Editor =function(renderer, session) {
|
|||
this.scrollToLine = function(line, center) {
|
||||
this.renderer.scrollToLine(line, center);
|
||||
};
|
||||
|
||||
|
||||
this.centerSelection = function() {
|
||||
var range = this.getSelectionRange();
|
||||
var line = Math.floor(range.start.row + (range.end.row - range.start.row) / 2);
|
||||
|
|
@ -912,7 +921,7 @@ var Editor =function(renderer, session) {
|
|||
this.selection.selectAll();
|
||||
this.$blockScrolling -= 1;
|
||||
};
|
||||
|
||||
|
||||
this.clearSelection = function() {
|
||||
this.selection.clearSelection();
|
||||
};
|
||||
|
|
@ -1039,7 +1048,7 @@ var Editor =function(renderer, session) {
|
|||
this.$blockScrolling += 1;
|
||||
for (var i = ranges.length - 1; i >= 0; --i)
|
||||
this.$tryReplace(ranges[i], replacement);
|
||||
|
||||
|
||||
this.selection.setSelectionRange(selection);
|
||||
this.$blockScrolling -= 1;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -73,12 +73,12 @@ var Gutter = function(parentEl) {
|
|||
|
||||
this.setAnnotations = function(annotations) {
|
||||
// iterate over sparse array
|
||||
this.$annotations = [];
|
||||
this.$annotations = [];
|
||||
for (var row in annotations) if (annotations.hasOwnProperty(row)) {
|
||||
var rowAnnotations = annotations[row];
|
||||
if (!rowAnnotations)
|
||||
continue;
|
||||
|
||||
|
||||
var rowInfo = this.$annotations[row] = {
|
||||
text: []
|
||||
};
|
||||
|
|
@ -111,6 +111,9 @@ var Gutter = function(parentEl) {
|
|||
annotation.className,
|
||||
"' title='", annotation.text.join("\n"),
|
||||
"' 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.style.height = config.minHeight + "px";
|
||||
|
|
|
|||
|
|
@ -186,18 +186,30 @@ var Text = function(parentEl) {
|
|||
var first = Math.max(firstRow, config.firstRow);
|
||||
var last = Math.min(lastRow, config.lastRow);
|
||||
|
||||
var lineElements = this.element.childNodes;
|
||||
var tokens = this.session.getTokens(first, last);
|
||||
var lineElements = this.element.childNodes,
|
||||
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++) {
|
||||
var lineElement = lineElements[i - config.firstRow];
|
||||
var lineElement = lineElements[lineElementsIdx++];
|
||||
if (!lineElement)
|
||||
continue;
|
||||
|
||||
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.style.height =
|
||||
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) {
|
||||
var fragment = document.createDocumentFragment();
|
||||
var tokens = this.session.getTokens(firstRow, lastRow);
|
||||
for (var row=firstRow; row<=lastRow; row++) {
|
||||
var lineEl = dom.createElement("div");
|
||||
|
||||
lineEl.className = "ace_line";
|
||||
var style = lineEl.style;
|
||||
style.height = this.session.getRowHeight(config, row) + "px";
|
||||
style.width = config.width + "px";
|
||||
|
||||
var html = [];
|
||||
if (tokens.length > row-firstRow)
|
||||
this.$renderLine(html, row, tokens[row-firstRow].tokens);
|
||||
// Get the tokens per line as there might be some lines in between
|
||||
// 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
|
||||
lineEl.innerHTML = html.join("");
|
||||
fragment.appendChild(lineEl);
|
||||
|
|
@ -276,6 +294,13 @@ var Text = function(parentEl) {
|
|||
};
|
||||
|
||||
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,
|
||||
characterWidth = this.config.characterWidth,
|
||||
screenColumn = 0;
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ var VirtualRenderer = function(container, theme) {
|
|||
this.getPrintMarginColumn = function() {
|
||||
return this.$printMarginColumn;
|
||||
};
|
||||
|
||||
|
||||
this.getShowGutter = function(){
|
||||
return this.showGutter;
|
||||
}
|
||||
|
|
@ -590,7 +590,7 @@ var VirtualRenderer = function(container, theme) {
|
|||
// the editor is not visible
|
||||
if (this.$size.scrollerHeight === 0)
|
||||
return;
|
||||
|
||||
|
||||
var pos = this.$cursorLayer.getPixelPosition();
|
||||
|
||||
var left = pos.left + this.$padding;
|
||||
|
|
@ -644,7 +644,7 @@ var VirtualRenderer = function(container, theme) {
|
|||
for (var l = 1; l < line; l++) {
|
||||
offset += this.session.getRowHeight(lineHeight, l-1);
|
||||
}
|
||||
|
||||
|
||||
if (center) {
|
||||
offset -= this.$size.scrollerHeight / 2;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue