diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 7d1b7fa6..be444747 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -107,14 +107,8 @@ var Cursor = function(parentEl) { top : 0 }; } - - var cursorLeft = Math.round(this.position.column * this.config.characterWidth); - var cursorTop = this.position.row * this.config.lineHeight; - - return { - left : cursorLeft, - top : cursorTop - }; + + return this.config.getPixelPosition(this.position.row, this.position.column); }; this.update = function(config) { @@ -123,17 +117,10 @@ var Cursor = function(parentEl) { this.config = config; - var cursorLeft = Math.round(this.position.column * config.characterWidth); - var cursorTop = this.position.row * config.lineHeight; + this.pixelPos = this.getPixelPosition(); - this.pixelPos = { - left : cursorLeft, - top : cursorTop - }; - - this.cursor.style.left = cursorLeft + "px"; - this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight)) - + "px"; + this.cursor.style.left = this.pixelPos.left + "px"; + this.cursor.style.top = this.pixelPos.top + "px"; this.cursor.style.width = config.characterWidth + "px"; this.cursor.style.height = config.lineHeight + "px"; diff --git a/lib/ace/layer/gutter.js b/lib/ace/layer/gutter.js index 4202772f..e4591e01 100644 --- a/lib/ace/layer/gutter.js +++ b/lib/ace/layer/gutter.js @@ -71,7 +71,7 @@ var Gutter = function(parentEl) { html.push("
", (i+1), "
"); + "' style='height:", (config.wrapped[i].length + 1) * config.lineHeight, "px;'>", (i+1), ""); html.push(""); } diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 06422bd5..be12014f 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -85,6 +85,10 @@ var Marker = function(parentEl) { var range = marker.range.clipRows(config.firstRow, config.lastRow); 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 (marker.type == "text") { @@ -121,7 +125,8 @@ var Marker = function(parentEl) { }; 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 var height = layerConfig.lineHeight; @@ -163,7 +168,8 @@ var Marker = function(parentEl) { }; 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 width = Math.round((range.end.column - range.start.column) * layerConfig.characterWidth); diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 98ff7bfc..371cd7e6 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -99,7 +99,7 @@ var Text = function(parentEl) { style.width = style.height = "auto"; style.left = style.top = "-1000px"; - + style.visibility = "hidden"; style.position = "absolute"; style.overflow = "visible"; @@ -151,7 +151,7 @@ var Text = function(parentEl) { this.updateLines = function(layerConfig, firstRow, lastRow) { this.$computeTabString(); this.config = layerConfig; - + var first = Math.max(firstRow, layerConfig.firstRow); var last = Math.min(lastRow, layerConfig.lastRow); @@ -166,6 +166,9 @@ var Text = function(parentEl) { var html = []; _self.$renderLine(html, i, tokens[i-first].tokens); 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"); lineEl.className = "ace_line"; 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"; var html = []; @@ -244,15 +247,20 @@ var Text = function(parentEl) { var html = []; var _self = this; - this.tokenizer.getTokens(config.firstRow, config.lastRow, function(tokens) { - for ( var i = config.firstRow; i <= config.lastRow; i++) { - html.push("
"); - _self.$renderLine(html, i, tokens[i-config.firstRow].tokens), html.push("
"); - } - - _self.element.innerHTML = html.join(""); + this.$renderLinesFragment(config, config.firstRow, config.lastRow, function(fragment) { + // TODO: Use a proper method to remove all children of the element. + _self.element.innerHTML = ""; + _self.element.appendChild(fragment); }); + // this.tokenizer.getTokens(config.firstRow, config.lastRow, function(tokens) { + // for ( var i = config.firstRow; i <= config.lastRow; i++) { + // html.push("
"); + // _self.$renderLine(html, i, tokens[i-config.firstRow].tokens), html.push("
"); + // } + // + // _self.element.innerHTML = html.join(""); + // }); }; this.$textToken = { @@ -262,6 +270,8 @@ var Text = function(parentEl) { }; this.$renderLine = function(stringBuilder, row, tokens) { + stringBuilder.push("
"); + var wrappedInfo = this.config.wrapped[row]; // if (this.$showInvisibles) { // var self = this; // 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 = " "; // } - for ( var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - - var output = token.value + var _self = this; + function addToken(token, value) { + var output = value .replace(/&/g, "&") .replace(/", output, ""); } else { 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("
"); + wrapSection ++; + maxChars = wrappedInfo[wrapSection] || 9999; + } + if (value.length != 0) { + chars += value.length; + addToken(token, value); + } + } }; if (this.$showInvisibles) { @@ -300,6 +336,7 @@ var Text = function(parentEl) { stringBuilder.push("" + this.EOF_CHAR + ""); } } + stringBuilder.push("
"); }; }).call(Text.prototype); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 03c3322e..f3552a21 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -82,7 +82,7 @@ var VirtualRenderer = function(container, theme) { this.$cursorLayer = new CursorLayer(this.content); this.layers = [ this.$markerLayer, textLayer, this.$cursorLayer ]; - + this.scrollBar = new ScrollBar(container); this.scrollBar.addEventListener("scroll", this.onScroll.bind(this)); @@ -118,6 +118,9 @@ var VirtualRenderer = function(container, theme) { }; (function() { + this.layerConfig = { + wrapped: [] + }; this.showGutter = true; @@ -142,10 +145,26 @@ var VirtualRenderer = function(container, theme) { 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 */ this.updateLines = function(firstRow, lastRow) { + this.$updateWrappedLinesInfo(firstRow, lastRow); + console.log("updateLines", firstRow, lastRow); if (lastRow === undefined) lastRow = Infinity; @@ -400,7 +419,7 @@ var VirtualRenderer = function(container, theme) { 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 layerConfig = this.layerConfig = { + var layerConfig = oop.mixin(this.layerConfig, { width : longestLine, padding : this.$padding, firstRow : firstRow, @@ -410,7 +429,11 @@ var VirtualRenderer = function(container, theme) { minHeight : minHeight, offset : offset, 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++) { var layer = this.layers[i]; @@ -570,10 +593,12 @@ var VirtualRenderer = function(container, theme) { var row = Math.floor((pageY + this.scrollTop - canvasPos.top) / this.lineHeight); - return { - row : row, - column : this.doc.screenToDocumentColumn(Math.max(0, Math.min(row, this.doc.getLength()-1)), col) - }; + return this.layerConfig.wrappedPosToPos( + 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) { @@ -587,6 +612,65 @@ var VirtualRenderer = function(container, theme) { pageY: canvasPos.top + y - this.getScrollTop() } }; + + 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() { dom.addCssClass(this.container, "ace_focus"); diff --git a/support/paths.js b/support/paths.js index 1be92a90..552c7022 100644 --- a/support/paths.js +++ b/support/paths.js @@ -4,4 +4,5 @@ require.paths.unshift(__dirname + "/../plugins"); require.paths.unshift(__dirname + "/async/lib"); require.paths.unshift(__dirname + "/node-htmlparser/lib"); require.paths.unshift(__dirname + "/jsdom/lib"); +require.paths.unshift(__dirname + "/cockpit/support/pilot/lib"); require.paths.unshift(__dirname);