From 29c553a45e07e7cf026cc070335bbaa46de90d67 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 29 Sep 2010 14:54:48 +0200 Subject: [PATCH 1/5] add changes bit mask --- src/ace/VirtualRenderer.js | 58 +++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 7da25171..7e67cf04 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -70,10 +70,20 @@ var VirtualRenderer = function(container) { }); ace.addListener(this.$gutter, "click", lang.bind(this.$onGutterClick, this)); ace.addListener(this.$gutter, "dblclick", lang.bind(this.$onGutterClick, this)); + + this.$changes = 0; }; (function() { + this.CHANGE_CURSOR = 1; + this.CHANGE_MARKER = 2; + this.CHANGE_TEXT = 4; + this.CHANGE_SCROLL = 8; + this.CHANGE_SIZE = 16 + this.CHANGE_LINES = 32; + this.CHANGE_FULL = 64; + ace.implement(this, MEventEmitter); this.setDocument = function(doc) { @@ -82,10 +92,14 @@ var VirtualRenderer = function(container) { this.$cursorLayer.setDocument(doc); this.$markerLayer.setDocument(doc); this.$textLayer.setDocument(doc); + + this.$changes = this.$changes & this.CHANGE_FULL; }; this.setTokenizer = function(tokenizer) { this.$textLayer.setTokenizer(tokenizer); + + this.$changes = this.$changes & this.CHANGE_TEXT; }; this.$onGutterClick = function(e) { @@ -105,6 +119,8 @@ var VirtualRenderer = function(container) { this.setShowInvisibles = function(showInvisibles) { this.$showInvisibles = showInvisibles; this.$textLayer.setShowInvisibles(showInvisibles); + + this.$changes = this.$changes & this.CHANGE_TEXT; }; this.getShowInvisibles = function() { @@ -176,7 +192,8 @@ var VirtualRenderer = function(container) { if (this.doc) { this.$updateScrollBar(); this.scrollToY(this.getScrollTop()); - this.draw(); + + this.$changes = this.$changes & this.CHANGE_SIZE; } }; @@ -190,6 +207,11 @@ var VirtualRenderer = function(container) { }; this.updateLines = function(firstRow, lastRow) { + // TODO + this.$changes = this.$changes & this.CHANGE_FULL; + }; + + this.$updateLines = function(firstRow, lastRow) { var layerConfig = this.layerConfig; // if the update changes the width of the document do a full redraw @@ -210,27 +232,14 @@ var VirtualRenderer = function(container) { }; this.draw = function(scrollOnly, callback) { - this.$draw(scrollOnly); + + if (scrollOnly) + this.$changes = this.$changes & this.CHANGE_SCROLL; + else + this.$changes = this.$changes & this.CHANGE_FULL; + +// this.$draw(scrollOnly); callback && callback(); -// if (this.$drawTimer) { -// clearInterval(this.$drawTimer); -// this.scrollOnly = this.scrollOnly && scrollOnly; -// } else { -// this.scollOnly = scrollOnly; -// } -// -// if (callback) -// this.$drawCallbacks.push(callback); -// -// var _self = this; -// this.$drawTimer = setTimeout(function() { -// _self.$draw(_self.scrollOnly); -// for (var i=0; i<_self.$drawCallbacks.length; i++) -// _self.$drawCallbacks[i](); -// -// _self.$drawCallbacks = []; -// delete _self.$drawTimer; -// }, 0); }; this.$draw = function(scrollOnly) { @@ -290,19 +299,23 @@ var VirtualRenderer = function(container) { this.addMarker = function(range, clazz, type) { return this.$markerLayer.addMarker(range, clazz, type); + this.$changes = this.$changes & this.CHANGE_MARKER; }; this.removeMarker = function(markerId) { this.$markerLayer.removeMarker(markerId); + this.$changes = this.$changes & this.CHANGE_MARKER; }; this.setBreakpoints = function(rows) { this.$gutterLayer.setBreakpoints(rows); + this.$changes = this.$changes & this.CHANGE_GUTTER; }; this.updateCursor = function(position, overwrite) { this.$cursorLayer.setCursor(position, overwrite); this.$cursorLayer.update(this.layerConfig); + this.$changes = this.$changes & this.CHANGE_CURSOR; }; this.hideCursor = function() { @@ -359,7 +372,8 @@ var VirtualRenderer = function(container) { if (this.scrollTop !== scrollTop) { this.scrollTop = scrollTop; this.$updateScrollBar(); - this.draw(true); + + this.$changes = this.$changes & this.CHANGE_SCROLL; } }; From eea7da558dcd7fc582027a407cd276207be227f8 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 29 Sep 2010 15:00:58 +0200 Subject: [PATCH 2/5] unify render updates --- src/ace/Editor.js | 17 ++-- src/ace/RenderLoop.js | 47 +++++++++++ src/ace/VirtualRenderer.js | 159 ++++++++++++++++++++++--------------- 3 files changed, 149 insertions(+), 74 deletions(-) create mode 100644 src/ace/RenderLoop.js diff --git a/src/ace/Editor.js b/src/ace/Editor.js index ba61e99c..de2304e7 100644 --- a/src/ace/Editor.js +++ b/src/ace/Editor.js @@ -104,7 +104,7 @@ var Editor = function(renderer, doc) { this.$onDocumentModeChange = ace.bind(this.onDocumentModeChange, this); doc.addEventListener("changeMode", this.$onDocumentModeChange); - this.$onDocumentChangeTabSize = ace.bind(this.renderer.draw, this.renderer); + this.$onDocumentChangeTabSize = ace.bind(this.renderer.updateText, this.renderer); doc.addEventListener("changeTabSize", this.$onDocumentChangeTabSize); this.$onDocumentChangeBreakpoint = ace.bind(this.onDocumentChangeBreakpoint, this); @@ -123,14 +123,11 @@ var Editor = function(renderer, doc) { this.bgTokenizer.setLines(this.doc.lines); this.bgTokenizer.start(0); - var _self = this; - _self.renderer.scrollToRow(doc.getScrollTopRow()); - this.renderer.draw(false, function() { - _self.onCursorChange(); - _self.onSelectionChange(); - _self.onDocumentChangeBreakpoint(); - _self.renderer.scrollToRow(doc.getScrollTopRow()); - }); + this.onCursorChange(); + this.onSelectionChange(); + this.onDocumentChangeBreakpoint(); + this.renderer.scrollToRow(doc.getScrollTopRow()); + this.renderer.updateFull(); }; this.getDocument = function() { @@ -258,7 +255,6 @@ var Editor = function(renderer, doc) { } this.renderer.setTokenizer(this.bgTokenizer); - this.renderer.draw(); }; @@ -453,7 +449,6 @@ var Editor = function(renderer, doc) { return; this.renderer.setShowInvisibles(showInvisibles); - this.renderer.draw(); }; this.getShowInvisibles = function() { diff --git a/src/ace/RenderLoop.js b/src/ace/RenderLoop.js new file mode 100644 index 00000000..47dbcca8 --- /dev/null +++ b/src/ace/RenderLoop.js @@ -0,0 +1,47 @@ +/** + * Ajax.org Code Editor (ACE) + * + * @copyright 2010, Ajax.org Services B.V. + * @license LGPLv3 + * @author Fabian Jakobs + */ +require.def("ace/VirtualRenderer", + ["ace/lib/oop", + "ace/MEventEmitter"], + function(oop, MEventEmitter) { + +var RenderLoop = function(fps) { + this.running = false; + this.interval = 1000 / fps; +} + +(function() { + + oop.implement(this, MEventEmitter); + + this.start = function() { + var _self = this; + this.stop(); + + this.running = true; + this.$timer = setTimeout(onTimeout, 0); + + function onTimeout() { + var start = new Date(); + _self.$dispatchEvent("tick"); + var end = new Date(); + var timeout = Math.max(10, _self.interval - (end - start)); + this.$timer = setTimeout(onTimeout, timeout); + } + }; + + this.stop = function() { + this.running = false; + if (this.$timer) + clearTimeout(this.$timer); + } + +}).call(RenderLoop.prototype); + +return RenderLoop; +}); \ No newline at end of file diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 7e67cf04..59436c6d 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -7,20 +7,21 @@ */ require.def("ace/VirtualRenderer", [ - "ace/ace", "ace/lib/oop", "ace/lib/lang", + "ace/lib/dom", + "ace/lib/event", "ace/layer/Gutter", "ace/layer/Marker", "ace/layer/Text", "ace/layer/Cursor", "ace/ScrollBar", "ace/MEventEmitter" - ], function(ace, oop, lang, GutterLayer, MarkerLayer, TextLayer, CursorLayer, ScrollBar, MEventEmitter) { + ], function(oop, lang, dom, event, GutterLayer, MarkerLayer, TextLayer, CursorLayer, ScrollBar, MEventEmitter) { var VirtualRenderer = function(container) { this.container = container; - ace.addCssClass(this.container, "ace_editor"); + dom.addCssClass(this.container, "ace_editor"); this.scroller = document.createElement("div"); this.scroller.className = "ace_scroller"; @@ -57,8 +58,6 @@ var VirtualRenderer = function(container) { column : 0 }; - this.$drawCallbacks = []; - this.$updatePrintMargin(); this.onResize(); @@ -68,23 +67,28 @@ var VirtualRenderer = function(container) { self.lineHeight = textLayer.getLineHeight(); self.onResize(); }); - ace.addListener(this.$gutter, "click", lang.bind(this.$onGutterClick, this)); - ace.addListener(this.$gutter, "dblclick", lang.bind(this.$onGutterClick, this)); + event.addListener(this.$gutter, "click", lang.bind(this.$onGutterClick, this)); + event.addListener(this.$gutter, "dblclick", lang.bind(this.$onGutterClick, this)); this.$changes = 0; + this.$size = { + width: 0, + height: 0 + }; }; (function() { this.CHANGE_CURSOR = 1; this.CHANGE_MARKER = 2; - this.CHANGE_TEXT = 4; + this.CHANGE_GUTTER = 4; this.CHANGE_SCROLL = 8; - this.CHANGE_SIZE = 16 - this.CHANGE_LINES = 32; - this.CHANGE_FULL = 64; + this.CHANGE_LINES = 16; + this.CHANGE_TEXT = 32; + this.CHANGE_SIZE = 64 + this.CHANGE_FULL = 128; - ace.implement(this, MEventEmitter); + oop.implement(this, MEventEmitter); this.setDocument = function(doc) { this.lines = doc.lines; @@ -93,18 +97,84 @@ var VirtualRenderer = function(container) { this.$markerLayer.setDocument(doc); this.$textLayer.setDocument(doc); - this.$changes = this.$changes & this.CHANGE_FULL; + this.$changes = this.$changes | this.CHANGE_FULL; + }; + + /** + * Triggers partial update of the text layer + */ + this.updateLines = function(firstRow, lastRow) { + if (!this.$updateLines) { + this.$updateLines = { + firstRow: firstRow, + lastRow: lastRow + } + } + else { + if (this.$updateLines.firstRow > firstRow) + this.$updateLines.firstRow = firstRow; + + if (this.$updateLines.lastRow < lastRow) + this.$updateLines.lastRow = lastRow; + } + + this.$changes = this.$changes | this.CHANGE_FULL; + }; + + /** + * Triggers full update of the text layer + */ + this.updateText = function() { + this.$changes = this.$changes | this.CHANGE_TEXT; + }; + + /** + * Triggers a full update of all layers + */ + this.updateFull = function() { + this.$changes = this.$changes | this.CHANGE_FULL; + }; + + /** + * Triggers resize of the editor + */ + this.onResize = function() { + this.$changes = this.$changes | this.CHANGE_SIZE; + + var height = dom.getInnerHeight(this.container); + if (this.$size.height != height) { + this.$size.height = height; + + this.scroller.style.height = height + "px"; + this.scrollBar.setHeight(height); + + if (this.doc) { + this.$updateScrollBar(); + this.scrollToY(this.getScrollTop()); + + this.$changes = this.$changes | this.CHANGE_FULL; + } + } + + var width = dom.getInnerWidth(this.container); + if (this.$size.width != width) { + this.$size.width = width; + + var gutterWidth = this.$gutter.offsetWidth; + this.scroller.style.left = gutterWidth + "px"; + this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px"; + } }; this.setTokenizer = function(tokenizer) { this.$textLayer.setTokenizer(tokenizer); - this.$changes = this.$changes & this.CHANGE_TEXT; + this.$changes = this.$changes | this.CHANGE_TEXT; }; this.$onGutterClick = function(e) { - var pageX = ace.getDocumentX(e); - var pageY = ace.getDocumentY(e); + var pageX = event.getDocumentX(e); + var pageY = event.getDocumentY(e); var event = { row: this.screenToTextCoordinates(pageX, pageY).row, @@ -120,7 +190,7 @@ var VirtualRenderer = function(container) { this.$showInvisibles = showInvisibles; this.$textLayer.setShowInvisibles(showInvisibles); - this.$changes = this.$changes & this.CHANGE_TEXT; + this.$changes = this.$changes | this.CHANGE_TEXT; }; this.getShowInvisibles = function() { @@ -178,25 +248,6 @@ var VirtualRenderer = function(container) { return this.layerConfig.lastRow || 0; }; - this.onResize = function() - { - var height = ace.getInnerHeight(this.container); - this.scroller.style.height = height + "px"; - this.scrollBar.setHeight(height); - - var width = ace.getInnerWidth(this.container); - var gutterWidth = this.$gutter.offsetWidth; - this.scroller.style.left = gutterWidth + "px"; - this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px"; - - if (this.doc) { - this.$updateScrollBar(); - this.scrollToY(this.getScrollTop()); - - this.$changes = this.$changes & this.CHANGE_SIZE; - } - }; - this.onScroll = function(e) { this.scrollToY(e.data); }; @@ -206,11 +257,6 @@ var VirtualRenderer = function(container) { this.scrollBar.setScrollTop(this.scrollTop); }; - this.updateLines = function(firstRow, lastRow) { - // TODO - this.$changes = this.$changes & this.CHANGE_FULL; - }; - this.$updateLines = function(firstRow, lastRow) { var layerConfig = this.layerConfig; @@ -223,7 +269,7 @@ var VirtualRenderer = function(container) { // if the last row is unknown -> redraw everything if (lastRow === undefined) { - this.draw(); + this.$draw(); return; } @@ -231,17 +277,6 @@ var VirtualRenderer = function(container) { this.$textLayer.updateLines(layerConfig, firstRow, lastRow); }; - this.draw = function(scrollOnly, callback) { - - if (scrollOnly) - this.$changes = this.$changes & this.CHANGE_SCROLL; - else - this.$changes = this.$changes & this.CHANGE_FULL; - -// this.$draw(scrollOnly); - callback && callback(); - }; - this.$draw = function(scrollOnly) { //var start = new Date(); @@ -299,23 +334,22 @@ var VirtualRenderer = function(container) { this.addMarker = function(range, clazz, type) { return this.$markerLayer.addMarker(range, clazz, type); - this.$changes = this.$changes & this.CHANGE_MARKER; + this.$changes = this.$changes | this.CHANGE_MARKER; }; this.removeMarker = function(markerId) { this.$markerLayer.removeMarker(markerId); - this.$changes = this.$changes & this.CHANGE_MARKER; + this.$changes = this.$changes | this.CHANGE_MARKER; }; this.setBreakpoints = function(rows) { this.$gutterLayer.setBreakpoints(rows); - this.$changes = this.$changes & this.CHANGE_GUTTER; + this.$changes = this.$changes | this.CHANGE_GUTTER; }; this.updateCursor = function(position, overwrite) { this.$cursorLayer.setCursor(position, overwrite); - this.$cursorLayer.update(this.layerConfig); - this.$changes = this.$changes & this.CHANGE_CURSOR; + this.$changes = this.$changes | this.CHANGE_CURSOR; }; this.hideCursor = function() { @@ -365,15 +399,14 @@ var VirtualRenderer = function(container) { }; this.scrollToY = function(scrollTop) { - var maxHeight = this.lines.length * this.lineHeight - - this.scroller.clientHeight; + var maxHeight = this.lines.length * this.lineHeight - this.scroller.clientHeight; var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); if (this.scrollTop !== scrollTop) { this.scrollTop = scrollTop; this.$updateScrollBar(); - this.$changes = this.$changes & this.CHANGE_SCROLL; + this.$changes = this.$changes | this.CHANGE_SCROLL; } }; @@ -397,11 +430,11 @@ var VirtualRenderer = function(container) { }; this.visualizeFocus = function() { - ace.addCssClass(this.container, "ace_focus"); + dom.addCssClass(this.container, "ace_focus"); }; this.visualizeBlur = function() { - ace.removeCssClass(this.container, "ace_focus"); + dom.removeCssClass(this.container, "ace_focus"); }; this.showComposition = function(position) { From de2a977a42405b65507373db318ecd2933616c30 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 29 Sep 2010 17:24:08 +0200 Subject: [PATCH 3/5] add convenience .on function as alias for addEventListener --- src/ace/MEventEmitter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ace/MEventEmitter.js b/src/ace/MEventEmitter.js index ba853710..4680a72b 100644 --- a/src/ace/MEventEmitter.js +++ b/src/ace/MEventEmitter.js @@ -26,6 +26,7 @@ require.def("ace/MEventEmitter", ["ace/lib/lang"], function(lang) { } }; + MEventEmitter.on = MEventEmitter.addEventListener = function(eventName, callback) { this.$eventRegistry = this.$eventRegistry || {}; From 0bc817cde188744317d3b0d5e3786738d732172c Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 29 Sep 2010 17:24:36 +0200 Subject: [PATCH 4/5] optimize render process --- src/ace/RenderLoop.js | 60 ++++++++----- src/ace/VirtualRenderer.js | 179 +++++++++++++++++++++++++------------ src/ace/layer/Cursor.js | 18 +++- src/ace/layer/Text.js | 25 ++---- 4 files changed, 185 insertions(+), 97 deletions(-) diff --git a/src/ace/RenderLoop.js b/src/ace/RenderLoop.js index 47dbcca8..546a9f16 100644 --- a/src/ace/RenderLoop.js +++ b/src/ace/RenderLoop.js @@ -5,40 +5,58 @@ * @license LGPLv3 * @author Fabian Jakobs */ -require.def("ace/VirtualRenderer", +require.def("ace/RenderLoop", ["ace/lib/oop", "ace/MEventEmitter"], function(oop, MEventEmitter) { var RenderLoop = function(fps) { - this.running = false; this.interval = 1000 / fps; -} +}; (function() { oop.implement(this, MEventEmitter); - this.start = function() { - var _self = this; - this.stop(); - - this.running = true; - this.$timer = setTimeout(onTimeout, 0); + if (window.mozRequestAnimationFrame) { - function onTimeout() { - var start = new Date(); - _self.$dispatchEvent("tick"); - var end = new Date(); - var timeout = Math.max(10, _self.interval - (end - start)); - this.$timer = setTimeout(onTimeout, timeout); - } - }; + this.start = function() { + var _self = this; + this.stop(); + + this.$onTimeout = function() { + _self.$dispatchEvent("tick"); + window.mozRequestAnimationFrame(); + } + window.addEventListener("MozBeforePaint", this.$onTimeout, false); + window.mozRequestAnimationFrame(); + }; - this.stop = function() { - this.running = false; - if (this.$timer) - clearTimeout(this.$timer); + this.stop = function() { + window.removeEventListener("MozBeforePaint", this.$onTimeout, false); + } + + } else { + + this.start = function() { + var _self = this; + this.stop(); + + this.$timer = setTimeout(onTimeout, 0); + + function onTimeout() { + var start = new Date(); + _self.$dispatchEvent("tick"); + var end = new Date(); + var timeout = Math.max(10, _self.interval - (end - start)); + this.$timer = setTimeout(onTimeout, timeout); + } + }; + + this.stop = function() { + if (this.$timer) + clearTimeout(this.$timer); + } } }).call(RenderLoop.prototype); diff --git a/src/ace/VirtualRenderer.js b/src/ace/VirtualRenderer.js index 59436c6d..b3be17f9 100644 --- a/src/ace/VirtualRenderer.js +++ b/src/ace/VirtualRenderer.js @@ -16,8 +16,11 @@ require.def("ace/VirtualRenderer", "ace/layer/Text", "ace/layer/Cursor", "ace/ScrollBar", + "ace/RenderLoop", "ace/MEventEmitter" - ], function(oop, lang, dom, event, GutterLayer, MarkerLayer, TextLayer, CursorLayer, ScrollBar, MEventEmitter) { + ], function( + oop, lang, dom, event, GutterLayer, MarkerLayer, TextLayer, + CursorLayer, ScrollBar, RenderLoop, MEventEmitter) { var VirtualRenderer = function(container) { this.container = container; @@ -58,23 +61,30 @@ var VirtualRenderer = function(container) { column : 0 }; - this.$updatePrintMargin(); - this.onResize(); - var self = this; this.$textLayer.addEventListener("changeCharaterSize", function() { self.characterWidth = textLayer.getCharacterWidth(); self.lineHeight = textLayer.getLineHeight(); - self.onResize(); + + this.$changes = this.$changes | this.CHANGE_FULL; }); event.addListener(this.$gutter, "click", lang.bind(this.$onGutterClick, this)); event.addListener(this.$gutter, "dblclick", lang.bind(this.$onGutterClick, this)); - this.$changes = 0; this.$size = { width: 0, - height: 0 + height: 0, + scrollerHeight: 0, + scrollerWidth: 0 }; + + this.$updatePrintMargin(); + + this.loop = new RenderLoop(50); + this.loop.on("tick", lang.bind(this.$renderChanges, this)); + this.loop.start(); + + this.$changes = this.CHANGE_FULL; }; (function() { @@ -149,9 +159,7 @@ var VirtualRenderer = function(container) { this.scrollBar.setHeight(height); if (this.doc) { - this.$updateScrollBar(); this.scrollToY(this.getScrollTop()); - this.$changes = this.$changes | this.CHANGE_FULL; } } @@ -164,6 +172,9 @@ var VirtualRenderer = function(container) { this.scroller.style.left = gutterWidth + "px"; this.scroller.style.width = Math.max(0, width - gutterWidth - this.scrollBar.getWidth()) + "px"; } + + this.$size.scrollerWidth = this.scroller.clientWidth; + this.$size.scrollerHeight = this.scroller.clientHeight; }; this.setTokenizer = function(tokenizer) { @@ -257,40 +268,87 @@ var VirtualRenderer = function(container) { this.scrollBar.setScrollTop(this.scrollTop); }; - this.$updateLines = function(firstRow, lastRow) { - var layerConfig = this.layerConfig; + this.$renderChanges = function() { + if (!this.$changes) + return; - // if the update changes the width of the document do a full redraw - if (layerConfig.width != this.$getLongestLine()) - return this.$draw(false); - - if (firstRow > layerConfig.lastRow + 1) { return; } - if (lastRow < layerConfig.firstRow) { return; } - - // if the last row is unknown -> redraw everything - if (lastRow === undefined) { - this.$draw(); + // text, scrolling and resize changes can cause the view port size to change + if (!this.layerConfig || + this.$changes & this.CHANGE_FULL || + this.$changes & this.CHANGE_SIZE || + this.$changes & this.CHANGE_TEXT || + this.$changes & this.CHANGE_LINES || + this.$changes & this.CHANGE_SCROLL + ) + this.$computeLayerConfig(); + + // full + if (this.$changes & this.CHANGE_FULL) { + this.$changes = 0; + this.$textLayer.update(this.layerConfig); + this.$gutterLayer.update(this.layerConfig); + this.$markerLayer.update(this.layerConfig); + this.$cursorLayer.update(this.layerConfig); + this.$updateScrollBar(); return; } + + // scrolling + if (this.$changes & this.CHANGE_SCROLL) { + if (this.$changes & this.CHANGE_TEXT || this.$changes & this.CHANGE_LINES) { + this.$textLayer.scrollLines(this.layerConfig); + this.$gutterLayer.update(this.layerConfig); + this.$markerLayer.update(this.layerConfig); + this.$cursorLayer.update(this.layerConfig); + } + else { + this.$textLayer.update(this.layerConfig); + this.$gutterLayer.update(this.layerConfig); + this.$markerLayer.update(this.layerConfig); + this.$cursorLayer.update(this.layerConfig); + } + this.$updateScrollBar(); + this.$changes = 0; + return; + } + + if (this.$changes & this.CHANGE_TEXT) { + this.$textLayer.update(this.layerConfig); + this.$gutterLayer.update(this.layerConfig); + } + else if (this.$changes & this.CHANGE_LINES) { + this.$updateLines(); + } + else if (this.$changes & this.CHANGE_SCROLL) { + this.$textLayer.scrollLines(this.layerConfig); + this.$gutterLayer.update(this.layerConfig); + } if (this.$changes & this.CHANGE_GUTTER) { + this.$gutterLayer.update(this.layerConfig); + } - // else update only the changed rows - this.$textLayer.updateLines(layerConfig, firstRow, lastRow); + if (this.$changes & this.CHANGE_CURSOR) + this.$cursorLayer.update(this.layerConfig); + + if (this.$changes & this.CHANGE_MARKER) { + this.$markerLayer.update(this.layerConfig); + } + + if (this.$changes & this.CHANGE_SIZE) + this.$updateScrollBar(); + + this.$changes = 0; }; - - this.$draw = function(scrollOnly) { - //var start = new Date(); - - var lines = this.lines; - + + this.$computeLayerConfig = function() { var offset = this.scrollTop % this.lineHeight; - var minHeight = this.scroller.clientHeight + offset; + var minHeight = this.$size.scrollerHeight + this.lineHeight; var longestLine = this.$getLongestLine(); - var widthChanged = this.layerConfig && (this.layerConfig.width != longestLine); + var widthChanged = !this.layerConfig ? true : (this.layerConfig.width != longestLine); var lineCount = Math.ceil(minHeight / this.lineHeight); var firstRow = Math.round((this.scrollTop - offset) / this.lineHeight); - var lastRow = Math.min(lines.length, firstRow + lineCount) - 1; + var lastRow = Math.min(this.lines.length, firstRow + lineCount) - 1; var layerConfig = this.layerConfig = { width : longestLine, @@ -298,40 +356,51 @@ var VirtualRenderer = function(container) { lastRow : lastRow, lineHeight : this.lineHeight, characterWidth : this.characterWidth, - minHeight : minHeight, - scrollOnly: !!scrollOnly + minHeight : minHeight }; - - this.content.style.marginTop = (-offset) + "px"; - this.content.style.height = minHeight + "px"; - + for ( var i = 0; i < this.layers.length; i++) { var layer = this.layers[i]; - if (widthChanged) { var style = layer.element.style; style.width = longestLine + "px"; } - - layer.update(layerConfig); }; - + this.$gutterLayer.element.style.marginTop = (-offset) + "px"; - this.$gutterLayer.update(layerConfig); + this.content.style.marginTop = (-offset) + "px"; + this.content.style.height = minHeight + "px"; + }; - //console.log("compute", new Date() - start, "ms") - this.$updateScrollBar(); - //console.log("compute+render", new Date() - start, "ms") + this.$updateLines = function() { + var firstRow = this.$updateLines.firstRow; + var lastRow = this.$updateLines.lastRow; + + var layerConfig = this.layerConfig; + + // if the update changes the width of the document do a full redraw + if (layerConfig.width != this.$getLongestLine()) + return this.$textLayer.update(layerConfig); + + if (firstRow > layerConfig.lastRow + 1) { return; } + if (lastRow < layerConfig.firstRow) { return; } + + // if the last row is unknown -> redraw everything + if (lastRow === undefined) + return this.$textLayer.update(layerConfig); + + // else update only the changed rows + this.$textLayer.updateLines(layerConfig, firstRow, lastRow); }; - + this.$getLongestLine = function() { var charCount = this.doc.getScreenWidth(); if (this.$showInvisibles) charCount += 1; - return Math.max(this.scroller.clientWidth, Math.round(charCount * this.characterWidth)); + return Math.max(this.$size.scrollerWidth, Math.round(charCount * this.characterWidth)); }; - + this.addMarker = function(range, clazz, type) { return this.$markerLayer.addMarker(range, clazz, type); this.$changes = this.$changes | this.CHANGE_MARKER; @@ -370,19 +439,19 @@ var VirtualRenderer = function(container) { this.scrollToY(top); } - if (this.getScrollTop() + this.scroller.clientHeight < top + if (this.getScrollTop() + this.$size.scrollerHeight < top + this.lineHeight) { - this.scrollToY(top + this.lineHeight - this.scroller.clientHeight); + this.scrollToY(top + this.lineHeight - this.$size.scrollerHeight); } if (this.scroller.scrollLeft > left) { this.scroller.scrollLeft = left; } - if (this.scroller.scrollLeft + this.scroller.clientWidth < left + if (this.scroller.scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) { this.scroller.scrollLeft = Math.round(left + this.characterWidth - - this.scroller.clientWidth); + - this.$size.scrollerWidth); } }, @@ -399,13 +468,11 @@ var VirtualRenderer = function(container) { }; this.scrollToY = function(scrollTop) { - var maxHeight = this.lines.length * this.lineHeight - this.scroller.clientHeight; + var maxHeight = this.lines.length * this.lineHeight - this.$size.scrollerHeight; var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); if (this.scrollTop !== scrollTop) { this.scrollTop = scrollTop; - this.$updateScrollBar(); - this.$changes = this.$changes | this.CHANGE_SCROLL; } }; diff --git a/src/ace/layer/Cursor.js b/src/ace/layer/Cursor.js index e39b6e61..4141269d 100644 --- a/src/ace/layer/Cursor.js +++ b/src/ace/layer/Cursor.js @@ -69,15 +69,27 @@ var Cursor = function(parentEl) { }; this.getPixelPosition = function() { - return this.pixelPos || { - left : 0, - top : 0 + if (!this.config || !this.position) { + return { + left : 0, + 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 }; }; this.update = function(config) { if (!this.position) return; + + this.config = config; var cursorLeft = Math.round(this.position.column * config.characterWidth); var cursorTop = this.position.row * config.lineHeight; diff --git a/src/ace/layer/Text.js b/src/ace/layer/Text.js index efe121ac..7f4197a6 100644 --- a/src/ace/layer/Text.js +++ b/src/ace/layer/Text.js @@ -126,12 +126,16 @@ var Text = function(parentEl) { }; }; - this.$scrollLines = function(oldConfig, config) { - if (oldConfig.lastRow < config.firstRow) - return this.$fullUpdate(config); + this.scrollLines = function(config) { + this.$computeTabString(); + var oldConfig = this.config; + this.config = config; + + if (!oldConfig || oldConfig.lastRow < config.firstRow) + return this.update(config); if (config.lastRow < oldConfig.firstRow) - return this.$fullUpdate(config); + return this.update(config); var el = this.element; @@ -175,21 +179,8 @@ var Text = function(parentEl) { }; this.update = function(config) { - if (!config.minHeight) - return; - this.$computeTabString(); - if (this.config && config.scrollOnly) { - this.$scrollLines(this.config, config); - } else { - this.$fullUpdate(config); - } - - this.config = config; - }; - - this.$fullUpdate = function(config) { var html = []; for ( var i = config.firstRow; i <= config.lastRow; i++) { html.push("