Move wrapping logging into Document.

This commit is contained in:
Julian Viereck 2011-01-08 21:14:51 +01:00
commit c30b9ece8f
8 changed files with 244 additions and 177 deletions

View file

@ -58,6 +58,7 @@ exports.launch = function(env) {
var docs = {}; var docs = {};
docs.js = new Document(document.getElementById("jstext").innerHTML); docs.js = new Document(document.getElementById("jstext").innerHTML);
docs.js.setUseWrapMode(true);
docs.js.setMode(new JavaScriptMode()); docs.js.setMode(new JavaScriptMode());
docs.js.setUndoManager(new UndoManager()); docs.js.setUndoManager(new UndoManager());

View file

@ -50,6 +50,7 @@ var Document = function(text, mode) {
this.lines = []; this.lines = [];
this.selection = new Selection(this); this.selection = new Selection(this);
this.$breakpoints = []; this.$breakpoints = [];
this.$wrapData = [];
this.listeners = []; this.listeners = [];
if (mode) { if (mode) {
@ -763,26 +764,72 @@ var Document = function(text, mode) {
return Math.max(0, Math.min(row, this.lines.length-1)); return Math.max(0, Math.min(row, this.lines.length-1));
}; };
this.documentToScreenColumn = function(row, docColumn) { }).call(Document.prototype);
var tabSize = this.getTabSize();
var screenColumn = 0;
var remaining = docColumn;
var line = this.getLine(row).split("\t"); (function() {
for (var i=0; i<line.length; i++) { this.$wrapLimit = 12;
var len = line[i].length; this.$useWrapMode = false;
if (remaining > len) { this.setUseWrapMode = function(useWrapMode) {
remaining -= (len + 1); var _self = this;
screenColumn += len + tabSize; function computeWrapData(e) {
} var lines = _self.lines, wrapData = _self.$wrapData;
else { var wrapLimit = _self.$wrapLimit;
screenColumn += remaining;
break; if (!e.data.lastRow) {
} e.data.lastRow = _self.lines.length - 1;
} }
return screenColumn; for (var row = e.data.firstRow; row <= e.data.lastRow; row++) {
var col = wrapLimit;
wrapData[row] = [];
while (col < lines[row].length) {
wrapData[row].push(col);
col += wrapLimit;
}
}
};
this.$useWrapMode = useWrapMode;
computeWrapData({ data: { firstRow: 0 } });
this._dispatchEvent("changeWrapMode");
if (useWrapMode) {
this.addEventListener("change", computeWrapData);
} else {
this.removeEventListener("change", computeWrapData);
}
};
this.getUseWrapMode = function() {
return this.$useWrapMode;
};
this.setWrapLimit = function(wrapLimit) {
this.$wrapLimit = wrapLimit;
};
this.getWrapLimit = function() {
return this.$wrapLimit;
};
this.getRowHeight = function(config, row) {
var rows;
if (!this.$useWrapMode) {
rows = 1;
} else {
rows = this.$wrapData[row].length + 1;
}
return rows * config.lineHeight;
};
this.getRowSplitData = function(row) {
if (!this.$useWrapMode) {
return undefined;
} else {
return this.$wrapData[row];
}
}; };
this.screenToDocumentColumn = function(row, screenColumn) { this.screenToDocumentColumn = function(row, screenColumn) {
@ -810,7 +857,96 @@ var Document = function(text, mode) {
return docColumn; return docColumn;
}; };
}).call(Document.prototype); this.screenToDocumentPosition = function(row, column) {
if (!this.$useWrapMode) {
return {
row: row,
column: this.screenToDocumentColumn(row, column)
}
}
var wrapData = this.$wrapData, linesCount = this.lines.length;
var docRow = 0;
while (docRow < linesCount && row >= wrapData[docRow].length + 1) {
row -= wrapData[docRow].length + 1;
docRow ++;
}
var docColumn = column +
(docRow < linesCount ? wrapData[docRow][row - 1] || 0 : 0);
return {
row: docRow,
column: docColumn
};
};
this.documentToScreenColumn = function(row, docColumn) {
var tabSize = this.getTabSize();
var screenColumn = 0;
var remaining = docColumn;
var line = this.getLine(row).split("\t");
for (var i=0; i<line.length; i++) {
var len = line[i].length;
if (remaining > len) {
remaining -= (len + 1);
screenColumn += len + tabSize;
}
else {
screenColumn += remaining;
break;
}
}
return screenColumn;
};
this.documentToScreenPosition = function(row, column) {
if (!this.$useWrapMode) {
return {
row: row,
column: this.documentToScreenColumn(row, column)
}
}
var wrapData = this.$wrapData;
var screenRow = 0;
// Handle special case where the row is outside of the range of lines.
if (row > wrapData.length - 1) {
for (row = 0; row < wrapData.length; row ++) {
screenRow += wrapData[row].length + 1;
}
return {
row: screenRow,
column: 0
}
}
for (var i = 0; i < row; i++) {
screenRow += wrapData[i].length + 1;
}
var screenColumn = column;
var wrapRowData = wrapData[row];
for (var split = 0; split < wrapRowData.length; split++) {
if (column > wrapRowData[split]) {
screenColumn = column - wrapRowData[split];
screenRow ++;
} else {
break;
}
}
return {
row: screenRow,
column: screenColumn
};
};
}).call(Document.prototype)
exports.Document = Document; exports.Document = Document;
}); });

View file

@ -108,7 +108,15 @@ var Cursor = function(parentEl) {
}; };
} }
return this.config.getPixelPosition(this.position.row, this.position.column); var pos = this.doc.documentToScreenPosition(this.position.row,
this.position.column);
var cursorLeft = Math.round(pos.column * this.config.characterWidth);
var cursorTop = pos.row * this.config.lineHeight;
return {
left : cursorLeft,
top : cursorTop
};
}; };
this.update = function(config) { this.update = function(config) {

View file

@ -48,6 +48,10 @@ var Gutter = function(parentEl) {
(function() { (function() {
this.setDocument = function(doc) {
this.doc = doc;
};
this.addGutterDecoration = function(row, className){ this.addGutterDecoration = function(row, className){
if (!this.$decorations[row]) if (!this.$decorations[row])
this.$decorations[row] = ""; this.$decorations[row] = "";
@ -71,7 +75,7 @@ var Gutter = function(parentEl) {
html.push("<div class='ace_gutter-cell", html.push("<div class='ace_gutter-cell",
this.$decorations[i] || "", this.$decorations[i] || "",
this.$breakpoints[i] ? " ace_breakpoint" : "", this.$breakpoints[i] ? " ace_breakpoint" : "",
"' style='height:", (config.wrapped[i].length + 1) * config.lineHeight, "px;'>", (i+1), "</div>"); "' style='height:", this.doc.getRowHeight(config, i) + "px'>", (i+1), "</div>");
html.push("</div>"); html.push("</div>");
} }

View file

@ -86,9 +86,7 @@ var Marker = function(parentEl) {
var range = marker.range.clipRows(config.firstRow, config.lastRow); var range = marker.range.clipRows(config.firstRow, config.lastRow);
if (range.isEmpty()) continue; if (range.isEmpty()) continue;
// TODO: Add this conversion to the range object directly! range = range.toScreenRange(this.doc);
range.start = this.config.posToWrappedPos(range.start.row, range.start.column);
range.end = this.config.posToWrappedPos(range.end.row, range.end.column);
if (range.isMultiLine()) { if (range.isMultiLine()) {
if (marker.type == "text") { if (marker.type == "text") {
@ -105,7 +103,6 @@ var Marker = function(parentEl) {
}; };
this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) { this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) {
// selection start // selection start
var row = range.start.row; var row = range.start.row;
var lineRange = new Range(row, range.start.column, row, this.doc.getLine(row).length); var lineRange = new Range(row, range.start.column, row, this.doc.getLine(row).length);
@ -125,9 +122,6 @@ var Marker = function(parentEl) {
}; };
this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) {
// TODO: Add this back.
// var range = range.toScreenRange(this.doc);
// from selection start to the end of the line // from selection start to the end of the line
var height = layerConfig.lineHeight; var height = layerConfig.lineHeight;
var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth)); var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth));
@ -168,9 +162,6 @@ var Marker = function(parentEl) {
}; };
this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig) { this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig) {
// TODO: Add this back.
//var range = range.toScreenRange(this.doc);
var height = layerConfig.lineHeight; var height = layerConfig.lineHeight;
var width = Math.round((range.end.column - range.start.column) * layerConfig.characterWidth); var width = Math.round((range.end.column - range.start.column) * layerConfig.characterWidth);
var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight;

View file

@ -148,18 +148,19 @@ var Text = function(parentEl) {
} }
}; };
this.updateLines = function(layerConfig, firstRow, lastRow) { this.updateLines = function(config, firstRow, lastRow) {
console.log("layer.text.updateLines", firstRow, lastRow);
this.$computeTabString(); this.$computeTabString();
this.config = layerConfig; this.config = config;
var first = Math.max(firstRow, layerConfig.firstRow); var first = Math.max(firstRow, config.firstRow);
var last = Math.min(lastRow, layerConfig.lastRow); var last = Math.min(lastRow, config.lastRow);
var lineElements = this.element.childNodes; var lineElements = this.element.childNodes;
var _self = this; var _self = this;
this.tokenizer.getTokens(first, last, function(tokens) { this.tokenizer.getTokens(first, last, function(tokens) {
for ( var i = first; i <= last; i++) { for ( var i = first; i <= last; i++) {
var lineElement = lineElements[i - layerConfig.firstRow]; var lineElement = lineElements[i - config.firstRow];
if (!lineElement) if (!lineElement)
continue; continue;
@ -168,7 +169,8 @@ var Text = function(parentEl) {
lineElement.innerHTML = html.join(""); lineElement.innerHTML = html.join("");
// The height of the line might have changed if wrapped mode // The height of the line might have changed if wrapped mode
// is active. // is active.
lineElement.style.height = (layerConfig.wrapped[i].length + 1) * layerConfig.lineHeight + "px"; lineElement.style.height =
_self.doc.getRowHeight(config, i) + "px";
} }
}); });
}; };
@ -229,7 +231,7 @@ var Text = function(parentEl) {
var lineEl = document.createElement("div"); var lineEl = document.createElement("div");
lineEl.className = "ace_line"; lineEl.className = "ace_line";
var style = lineEl.style; var style = lineEl.style;
style.height = (config.wrapped[row].length + 1) * config.lineHeight + "px"; style.height = _self.doc.getRowHeight(config, row) + "px";
style.width = config.width + "px"; style.width = config.width + "px";
var html = []; var html = [];
@ -242,6 +244,7 @@ var Text = function(parentEl) {
}; };
this.update = function(config) { this.update = function(config) {
console.log("layer.text.update()");
this.$computeTabString(); this.$computeTabString();
this.config = config; this.config = config;
@ -270,8 +273,6 @@ var Text = function(parentEl) {
}; };
this.$renderLine = function(stringBuilder, row, tokens) { this.$renderLine = function(stringBuilder, row, tokens) {
stringBuilder.push("<div>");
var wrappedInfo = this.config.wrapped[row];
// if (this.$showInvisibles) { // if (this.$showInvisibles) {
// var self = this; // var self = this;
// var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]+/g; // var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]+/g;
@ -302,25 +303,31 @@ var Text = function(parentEl) {
} }
} }
var chars = 0; var splits = this.doc.getRowSplitData(row);
var wrapSection = 0; var chars = 0, split = 0, splitChars;
var maxChars = wrappedInfo[wrapSection] || 9999;
var value; if (!splits || splits.length == 0) {
splitChars = Number.MAX_VALUE;
} else {
splitChars = splits[0];
}
stringBuilder.push("<div>");
for (var i = 0; i < tokens.length; i++) { for (var i = 0; i < tokens.length; i++) {
var token = tokens[i]; var token = tokens[i];
var value = token.value;
if (chars + token.value.length < maxChars) { if (chars + value.length < splitChars) {
addToken(token, token.value); addToken(token, value);
chars += token.value.length; chars += value.length;
} else { } else {
value = token.value; while (chars + value.length >= splitChars) {
while (chars + value.length >= maxChars) { addToken(token, value.substring(0, splitChars - chars));
addToken(token, value.substring(0, maxChars - chars)); value = value.substring(splitChars - chars);
value = value.substring(maxChars - chars); chars = splitChars;
chars = maxChars;
stringBuilder.push("</div><div>"); stringBuilder.push("</div><div>");
wrapSection ++; split ++;
maxChars = wrappedInfo[wrapSection] || 9999; splitChars = splits[split] || Number.MAX_VALUE;
} }
if (value.length != 0) { if (value.length != 0) {
chars += value.length; chars += value.length;

View file

@ -146,9 +146,13 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
}; };
this.toScreenRange = function(doc) { this.toScreenRange = function(doc) {
var screenPosStart =
doc.documentToScreenPosition(this.start.row, this.start.column);
var screenPosEnd =
doc.documentToScreenPosition(this.end.row, this.end.column);
return new Range( return new Range(
this.start.row, doc.documentToScreenColumn(this.start.row, this.start.column), screenPosStart.row, screenPosStart.column,
this.end.row, doc.documentToScreenColumn(this.end.row, this.end.column) screenPosEnd.row, screenPosEnd.column
); );
}; };

View file

@ -118,10 +118,6 @@ var VirtualRenderer = function(container, theme) {
}; };
(function() { (function() {
this.layerConfig = {
wrapped: []
};
this.showGutter = true; this.showGutter = true;
this.CHANGE_CURSOR = 1; this.CHANGE_CURSOR = 1;
@ -140,30 +136,16 @@ var VirtualRenderer = function(container, theme) {
this.doc = doc; this.doc = doc;
this.$cursorLayer.setDocument(doc); this.$cursorLayer.setDocument(doc);
this.$markerLayer.setDocument(doc); this.$markerLayer.setDocument(doc);
this.$gutterLayer.setDocument(doc);
this.$textLayer.setDocument(doc); this.$textLayer.setDocument(doc);
this.$loop.schedule(this.CHANGE_FULL); this.$loop.schedule(this.CHANGE_FULL);
}; };
this.$updateWrappedLinesInfo = function(firstRow, lastRow) {
var WRAPSIZE = 12;
var wrappedInfo = this.layerConfig.wrapped;
var lines = this.lines;
for (var row = firstRow; row <= lastRow; row++) {
var col = 12;
wrappedInfo[row] = [];
while (col < lines[row].length) {
wrappedInfo[row].push(col);
col += 12;
}
}
};
/** /**
* Triggers partial update of the text layer * Triggers partial update of the text layer
*/ */
this.updateLines = function(firstRow, lastRow) { this.updateLines = function(firstRow, lastRow) {
this.$updateWrappedLinesInfo(firstRow, lastRow);
console.log("updateLines", firstRow, lastRow); console.log("updateLines", firstRow, lastRow);
if (lastRow === undefined) if (lastRow === undefined)
lastRow = Infinity; lastRow = Infinity;
@ -419,7 +401,7 @@ var VirtualRenderer = function(container, theme) {
var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight)); var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight));
var lastRow = Math.max(0, Math.min(this.lines.length, firstRow + lineCount) - 1); var lastRow = Math.max(0, Math.min(this.lines.length, firstRow + lineCount) - 1);
var layerConfig = oop.mixin(this.layerConfig, { var layerConfig = this.layerConfig = {
width : longestLine, width : longestLine,
padding : this.$padding, padding : this.$padding,
firstRow : firstRow, firstRow : firstRow,
@ -429,11 +411,7 @@ var VirtualRenderer = function(container, theme) {
minHeight : minHeight, minHeight : minHeight,
offset : offset, offset : offset,
height : this.$size.scrollerHeight height : this.$size.scrollerHeight
}); };
// Ensure that there is a wrapped array for all the rows in the current
// view port.
this.$updateWrappedLinesInfo(firstRow, lastRow);
for ( var i = 0; i < this.layers.length; i++) { for ( var i = 0; i < this.layers.length; i++) {
var layer = this.layers[i]; var layer = this.layers[i];
@ -593,19 +571,16 @@ var VirtualRenderer = function(container, theme) {
var row = Math.floor((pageY + this.scrollTop - canvasPos.top) var row = Math.floor((pageY + this.scrollTop - canvasPos.top)
/ this.lineHeight); / this.lineHeight);
return this.layerConfig.wrappedPosToPos( return this.doc.screenToDocumentPosition(row, col);
row,
col
// TODO: Figure out how to calculate tabs here...
//this.doc.screenToDocumentColumn(Math.max(0, Math.min(row, this.doc.getLength()-1)), col)
);
}; };
this.textToScreenCoordinates = function(row, column) { this.textToScreenCoordinates = function(row, column) {
var canvasPos = this.scroller.getBoundingClientRect(); var canvasPos = this.scroller.getBoundingClientRect();
var pos = this.doc.documentToScreenPosition(row, column);
var x = this.padding + Math.round(this.doc.documentToScreenColumn(row, column) * this.characterWidth);
var y = row * this.lineHeight; var x = this.padding + Math.round(pos.column * this.characterWidth);
var y = pos.row * this.lineHeight;
return { return {
pageX: canvasPos.left + x - this.getScrollLeft(), pageX: canvasPos.left + x - this.getScrollLeft(),
@ -613,65 +588,6 @@ var VirtualRenderer = function(container, theme) {
} }
}; };
this.wrappedPosToPos = function(row, column) {
var linesCount = this.wrapped.length;
var realRow = 0;
while (realRow < linesCount && row >= this.wrapped[realRow].length + 1) {
row -= this.wrapped[realRow].length + 1;
realRow ++;
}
return {
row: realRow,
column: column + (realRow < linesCount ? this.wrapped[realRow][row - 1] || 0 : 0)
};
};
this.posToWrappedPos = function(row, column) {
// TODO: Why can it happen, that row is higher then the current count
// of lines (note lines in doc, not only in wrapped!). Happens when
// the cursor is in the last line and the marker "ace_active_line" is
// painted.
if (row > this.wrapped.length - 1) {
row = this.wrapped.length - 1;
column = 99999;
}
var rows = 0;
for (var i = 0; i < row; i++) {
rows += this.wrapped[i].length + 1;
}
var col = column;
for (var s = 0; s < this.wrapped[row].length; s++) {
if (column > this.wrapped[row][s]) {
col = column - this.wrapped[row][s];
rows ++;
} else {
break;
}
}
return {
row: rows,
column: col
};
};
this.getPixelPosition = function(row, column) {
var pos = this.posToWrappedPos(row, column);
var cursorLeft = Math.round(pos.column * this.characterWidth);
var cursorTop = pos.row * this.lineHeight;
return {
left : cursorLeft,
top : cursorTop
};
};
// TODO: This should get passed in a different way to the cursorLayer!
this.layerConfig.getPixelPosition = this.getPixelPosition;
this.layerConfig.posToWrappedPos = this.posToWrappedPos;
this.layerConfig.wrappedPosToPos = this.wrappedPosToPos;
this.visualizeFocus = function() { this.visualizeFocus = function() {
dom.addCssClass(this.container, "ace_focus"); dom.addCssClass(this.container, "ace_focus");
}; };