Simple line wrapping is up but a little bit of a hack right now

This commit is contained in:
Julian Viereck 2011-01-07 12:15:25 +01:00
commit 511de83908
6 changed files with 160 additions and 45 deletions

View file

@ -108,13 +108,7 @@ var Cursor = function(parentEl) {
}; };
} }
var cursorLeft = Math.round(this.position.column * this.config.characterWidth); return this.config.getPixelPosition(this.position.row, this.position.column);
var cursorTop = this.position.row * this.config.lineHeight;
return {
left : cursorLeft,
top : cursorTop
};
}; };
this.update = function(config) { this.update = function(config) {
@ -123,17 +117,10 @@ var Cursor = function(parentEl) {
this.config = config; this.config = config;
var cursorLeft = Math.round(this.position.column * config.characterWidth); this.pixelPos = this.getPixelPosition();
var cursorTop = this.position.row * config.lineHeight;
this.pixelPos = { this.cursor.style.left = this.pixelPos.left + "px";
left : cursorLeft, this.cursor.style.top = this.pixelPos.top + "px";
top : cursorTop
};
this.cursor.style.left = cursorLeft + "px";
this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight))
+ "px";
this.cursor.style.width = config.characterWidth + "px"; this.cursor.style.width = config.characterWidth + "px";
this.cursor.style.height = config.lineHeight + "px"; this.cursor.style.height = config.lineHeight + "px";

View file

@ -71,7 +71,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.lineHeight, "px;'>", (i+1), "</div>"); "' style='height:", (config.wrapped[i].length + 1) * config.lineHeight, "px;'>", (i+1), "</div>");
html.push("</div>"); html.push("</div>");
} }

View file

@ -86,6 +86,10 @@ 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.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") {
this.drawTextMarker(html, range, marker.clazz, config); this.drawTextMarker(html, range, marker.clazz, config);
@ -121,7 +125,8 @@ var Marker = function(parentEl) {
}; };
this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) {
var range = range.toScreenRange(this.doc); // 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;
@ -163,7 +168,8 @@ var Marker = function(parentEl) {
}; };
this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig) { this.drawSingleLineMarker = function(stringBuilder, range, clazz, layerConfig) {
var range = range.toScreenRange(this.doc); // 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);

View file

@ -166,6 +166,9 @@ var Text = function(parentEl) {
var html = []; var html = [];
_self.$renderLine(html, i, tokens[i-first].tokens); _self.$renderLine(html, i, tokens[i-first].tokens);
lineElement.innerHTML = html.join(""); lineElement.innerHTML = html.join("");
// The height of the line might have changed if wrapped mode
// is active.
lineElement.style.height = (layerConfig.wrapped[i].length + 1) * layerConfig.lineHeight + "px";
} }
}); });
}; };
@ -226,7 +229,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 = _self.$characterSize.height + "px"; style.height = (config.wrapped[row].length + 1) * config.lineHeight + "px";
style.width = config.width + "px"; style.width = config.width + "px";
var html = []; var html = [];
@ -244,15 +247,20 @@ var Text = function(parentEl) {
var html = []; var html = [];
var _self = this; var _self = this;
this.tokenizer.getTokens(config.firstRow, config.lastRow, function(tokens) { this.$renderLinesFragment(config, config.firstRow, config.lastRow, function(fragment) {
for ( var i = config.firstRow; i <= config.lastRow; i++) { // TODO: Use a proper method to remove all children of the element.
html.push("<div class='ace_line' style='height:" + _self.$characterSize.height + "px;", "width:", _self.element.innerHTML = "";
config.width, "px'>"); _self.element.appendChild(fragment);
_self.$renderLine(html, i, tokens[i-config.firstRow].tokens), html.push("</div>");
}
_self.element.innerHTML = html.join("");
}); });
// this.tokenizer.getTokens(config.firstRow, config.lastRow, function(tokens) {
// for ( var i = config.firstRow; i <= config.lastRow; i++) {
// html.push("<div class='ace_line' style='height:" + _self.$characterSize.height + "px;", "width:",
// config.width, "px'>");
// _self.$renderLine(html, i, tokens[i-config.firstRow].tokens), html.push("</div>");
// }
//
// _self.element.innerHTML = html.join("");
// });
}; };
this.$textToken = { this.$textToken = {
@ -262,6 +270,8 @@ 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;
@ -275,22 +285,48 @@ var Text = function(parentEl) {
var spaceReplace = "&nbsp;"; var spaceReplace = "&nbsp;";
// } // }
for ( var i = 0; i < tokens.length; i++) { var _self = this;
var token = tokens[i]; function addToken(token, value) {
var output = value
var output = token.value
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
.replace(spaceRe, spaceReplace) .replace(spaceRe, spaceReplace)
.replace(/\t/g, this.$tabString); .replace(/\t/g, _self.$tabString);
if (!this.$textToken[token.type]) { if (!_self.$textToken[token.type]) {
var classes = "ace_" + token.type.replace(/\./g, " ace_"); var classes = "ace_" + token.type.replace(/\./g, " ace_");
stringBuilder.push("<span class='", classes, "'>", output, "</span>"); stringBuilder.push("<span class='", classes, "'>", output, "</span>");
} }
else { else {
stringBuilder.push(output); stringBuilder.push(output);
} }
}
var chars = 0;
var wrapSection = 0;
var maxChars = wrappedInfo[wrapSection] || 9999;
var value;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (chars + token.value.length < maxChars) {
addToken(token, token.value);
chars += token.value.length;
} else {
value = token.value;
while (chars + value.length >= maxChars) {
addToken(token, value.substring(0, maxChars - chars));
value = value.substring(maxChars - chars);
chars = maxChars;
stringBuilder.push("</div><div>");
wrapSection ++;
maxChars = wrappedInfo[wrapSection] || 9999;
}
if (value.length != 0) {
chars += value.length;
addToken(token, value);
}
}
}; };
if (this.$showInvisibles) { if (this.$showInvisibles) {
@ -300,6 +336,7 @@ var Text = function(parentEl) {
stringBuilder.push("<span class='ace_invisible'>" + this.EOF_CHAR + "</span>"); stringBuilder.push("<span class='ace_invisible'>" + this.EOF_CHAR + "</span>");
} }
} }
stringBuilder.push("</div>");
}; };
}).call(Text.prototype); }).call(Text.prototype);

View file

@ -118,6 +118,9 @@ var VirtualRenderer = function(container, theme) {
}; };
(function() { (function() {
this.layerConfig = {
wrapped: []
};
this.showGutter = true; this.showGutter = true;
@ -142,10 +145,26 @@ var VirtualRenderer = function(container, theme) {
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);
if (lastRow === undefined) if (lastRow === undefined)
lastRow = Infinity; lastRow = Infinity;
@ -400,7 +419,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 = this.layerConfig = { var layerConfig = oop.mixin(this.layerConfig, {
width : longestLine, width : longestLine,
padding : this.$padding, padding : this.$padding,
firstRow : firstRow, firstRow : firstRow,
@ -410,7 +429,11 @@ 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];
@ -570,10 +593,12 @@ 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 { return this.layerConfig.wrappedPosToPos(
row : row, row,
column : this.doc.screenToDocumentColumn(Math.max(0, Math.min(row, this.doc.getLength()-1)), col) 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) {
@ -588,6 +613,65 @@ 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");
}; };

View file

@ -4,4 +4,5 @@ require.paths.unshift(__dirname + "/../plugins");
require.paths.unshift(__dirname + "/async/lib"); require.paths.unshift(__dirname + "/async/lib");
require.paths.unshift(__dirname + "/node-htmlparser/lib"); require.paths.unshift(__dirname + "/node-htmlparser/lib");
require.paths.unshift(__dirname + "/jsdom/lib"); require.paths.unshift(__dirname + "/jsdom/lib");
require.paths.unshift(__dirname + "/cockpit/support/pilot/lib");
require.paths.unshift(__dirname); require.paths.unshift(__dirname);