lib/gollum/public/gollum/livepreview/js/ace/lib/ace/virtual_renderer.js in gollum-3.1.2 vs lib/gollum/public/gollum/livepreview/js/ace/lib/ace/virtual_renderer.js in gollum-3.1.3
- old
+ new
@@ -1,22 +1,22 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
- *
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
@@ -31,56 +31,52 @@
define(function(require, exports, module) {
"use strict";
var oop = require("./lib/oop");
var dom = require("./lib/dom");
-var event = require("./lib/event");
-var useragent = require("./lib/useragent");
var config = require("./config");
-var net = require("./lib/net");
+var useragent = require("./lib/useragent");
var GutterLayer = require("./layer/gutter").Gutter;
var MarkerLayer = require("./layer/marker").Marker;
var TextLayer = require("./layer/text").Text;
var CursorLayer = require("./layer/cursor").Cursor;
-var ScrollBar = require("./scrollbar").ScrollBar;
+var HScrollBar = require("./scrollbar").HScrollBar;
+var VScrollBar = require("./scrollbar").VScrollBar;
var RenderLoop = require("./renderloop").RenderLoop;
+var FontMetrics = require("./layer/font_metrics").FontMetrics;
var EventEmitter = require("./lib/event_emitter").EventEmitter;
var editorCss = require("./requirejs/text!./css/editor.css");
dom.importCssString(editorCss, "ace_editor");
/**
- *
- *
* The class that is responsible for drawing everything you see on the screen!
+ * @related editor.renderer
* @class VirtualRenderer
**/
/**
* Constructs a new `VirtualRenderer` within the `container` specified, applying the given `theme`.
* @param {DOMElement} container The root element of the editor
* @param {String} theme The starting theme
*
- *
- *
- *
* @constructor
**/
var VirtualRenderer = function(container, theme) {
var _self = this;
- this.container = container;
+ this.container = container || dom.createElement("div");
// TODO: this breaks rendering in Cloud9 with multiple ace instances
-// // Imports CSS once per DOM document ('ace_editor' serves as an identifier).
-// dom.importCssString(editorCss, "ace_editor", container.ownerDocument);
+ // // Imports CSS once per DOM document ('ace_editor' serves as an identifier).
+ // dom.importCssString(editorCss, "ace_editor", container.ownerDocument);
// in IE <= 9 the native cursor always shines through
- this.$keepTextAreaAtCursor = !useragent.isIE;
+ this.$keepTextAreaAtCursor = !useragent.isOldIE;
- dom.addCssClass(container, "ace_editor");
+ dom.addCssClass(this.container, "ace_editor");
this.setTheme(theme);
this.$gutter = dom.createElement("div");
this.$gutter.className = "ace_gutter";
@@ -92,13 +88,12 @@
this.content = dom.createElement("div");
this.content.className = "ace_content";
this.scroller.appendChild(this.content);
- this.setHighlightGutterLine(true);
this.$gutterLayer = new GutterLayer(this.$gutter);
- this.$gutterLayer.on("changeGutterWidth", this.onResize.bind(this, true));
+ this.$gutterLayer.on("changeGutterWidth", this.onGutterResize.bind(this));
this.$markerBack = new MarkerLayer(this.content);
var textLayer = this.$textLayer = new TextLayer(this.content);
this.canvas = textLayer.element;
@@ -107,72 +102,85 @@
this.$cursorLayer = new CursorLayer(this.content);
// Indicates whether the horizontal scrollbar is visible
this.$horizScroll = false;
- this.$horizScrollAlwaysVisible = false;
+ this.$vScroll = false;
- this.$animatedScroll = false;
-
- this.scrollBar = new ScrollBar(container);
- this.scrollBar.addEventListener("scroll", function(e) {
- if (!_self.$inScrollAnimation)
- _self.session.setScrollTop(e.data);
+ this.scrollBar =
+ this.scrollBarV = new VScrollBar(this.container, this);
+ this.scrollBarH = new HScrollBar(this.container, this);
+ this.scrollBarV.addEventListener("scroll", function(e) {
+ if (!_self.$scrollAnimation)
+ _self.session.setScrollTop(e.data - _self.scrollMargin.top);
});
+ this.scrollBarH.addEventListener("scroll", function(e) {
+ if (!_self.$scrollAnimation)
+ _self.session.setScrollLeft(e.data - _self.scrollMargin.left);
+ });
this.scrollTop = 0;
this.scrollLeft = 0;
- event.addListener(this.scroller, "scroll", function() {
- var scrollLeft = _self.scroller.scrollLeft;
- _self.scrollLeft = scrollLeft;
- _self.session.setScrollLeft(scrollLeft);
- });
-
this.cursorPos = {
row : 0,
column : 0
};
- this.$textLayer.addEventListener("changeCharacterSize", function() {
+ this.$fontMetrics = new FontMetrics(this.container, 500);
+ this.$textLayer.$setFontMetrics(this.$fontMetrics);
+ this.$textLayer.addEventListener("changeCharacterSize", function(e) {
_self.updateCharacterSize();
- _self.onResize(true);
+ _self.onResize(true, _self.gutterWidth, _self.$size.width, _self.$size.height);
+ _self._signal("changeCharacterSize", e);
});
this.$size = {
width: 0,
height: 0,
scrollerHeight: 0,
- scrollerWidth: 0
+ scrollerWidth: 0,
+ $dirty: true
};
this.layerConfig = {
width : 1,
padding : 0,
firstRow : 0,
firstRowScreen: 0,
lastRow : 0,
- lineHeight : 1,
- characterWidth : 1,
+ lineHeight : 0,
+ characterWidth : 0,
minHeight : 1,
maxHeight : 1,
offset : 0,
- height : 1
+ height : 1,
+ gutterOffset: 1
};
+
+ this.scrollMargin = {
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ v: 0,
+ h: 0
+ };
this.$loop = new RenderLoop(
this.$renderChanges.bind(this),
this.container.ownerDocument.defaultView
);
this.$loop.schedule(this.CHANGE_FULL);
this.updateCharacterSize();
this.setPadding(4);
+ config.resetOptions(this);
+ config._emit("renderer", this);
};
(function() {
- this.showGutter = true;
this.CHANGE_CURSOR = 1;
this.CHANGE_MARKER = 2;
this.CHANGE_GUTTER = 4;
this.CHANGE_SCROLL = 8;
@@ -182,49 +190,77 @@
this.CHANGE_MARKER_BACK = 128;
this.CHANGE_MARKER_FRONT = 256;
this.CHANGE_FULL = 512;
this.CHANGE_H_SCROLL = 1024;
+ // this.$logChanges = function(changes) {
+ // var a = ""
+ // if (changes & this.CHANGE_CURSOR) a += " cursor";
+ // if (changes & this.CHANGE_MARKER) a += " marker";
+ // if (changes & this.CHANGE_GUTTER) a += " gutter";
+ // if (changes & this.CHANGE_SCROLL) a += " scroll";
+ // if (changes & this.CHANGE_LINES) a += " lines";
+ // if (changes & this.CHANGE_TEXT) a += " text";
+ // if (changes & this.CHANGE_SIZE) a += " size";
+ // if (changes & this.CHANGE_MARKER_BACK) a += " marker_back";
+ // if (changes & this.CHANGE_MARKER_FRONT) a += " marker_front";
+ // if (changes & this.CHANGE_FULL) a += " full";
+ // if (changes & this.CHANGE_H_SCROLL) a += " h_scroll";
+ // console.log(a.trim())
+ // };
+
oop.implement(this, EventEmitter);
-
+
this.updateCharacterSize = function() {
if (this.$textLayer.allowBoldFonts != this.$allowBoldFonts) {
this.$allowBoldFonts = this.$textLayer.allowBoldFonts;
this.setStyle("ace_nobold", !this.$allowBoldFonts);
}
-
+
+ this.layerConfig.characterWidth =
this.characterWidth = this.$textLayer.getCharacterWidth();
+ this.layerConfig.lineHeight =
this.lineHeight = this.$textLayer.getLineHeight();
this.$updatePrintMargin();
};
/**
- *
+ *
* Associates the renderer with an [[EditSession `EditSession`]].
**/
this.setSession = function(session) {
+ if (this.session)
+ this.session.doc.off("changeNewLineMode", this.onChangeNewLineMode);
+
this.session = session;
-
- this.scroller.className = "ace_scroller";
-
+ if (session && this.scrollMargin.top && session.getScrollTop() <= 0)
+ session.setScrollTop(-this.scrollMargin.top);
+
this.$cursorLayer.setSession(session);
this.$markerBack.setSession(session);
this.$markerFront.setSession(session);
this.$gutterLayer.setSession(session);
this.$textLayer.setSession(session);
+ if (!session)
+ return;
+
this.$loop.schedule(this.CHANGE_FULL);
+ this.session.$setFontMetrics(this.$fontMetrics);
+ this.onChangeNewLineMode = this.onChangeNewLineMode.bind(this);
+ this.onChangeNewLineMode()
+ this.session.doc.on("changeNewLineMode", this.onChangeNewLineMode);
};
/**
* Triggers a partial update of the text, from the range given by the two parameters.
* @param {Number} firstRow The first row to update
* @param {Number} lastRow The last row to update
*
- *
+ *
**/
- this.updateLines = function(firstRow, lastRow) {
+ this.updateLines = function(firstRow, lastRow, force) {
if (lastRow === undefined)
lastRow = Infinity;
if (!this.$changedLines) {
this.$changedLines = {
@@ -238,13 +274,30 @@
if (this.$changedLines.lastRow < lastRow)
this.$changedLines.lastRow = lastRow;
}
+ // If the change happened offscreen above us then it's possible
+ // that a new line wrap will affect the position of the lines on our
+ // screen so they need redrawn.
+ // TODO: better solution is to not change scroll position when text is changed outside of visible area
+ if (this.$changedLines.lastRow < this.layerConfig.firstRow) {
+ if (force)
+ this.$changedLines.lastRow = this.layerConfig.lastRow;
+ else
+ return;
+ }
+ if (this.$changedLines.firstRow > this.layerConfig.lastRow)
+ return;
this.$loop.schedule(this.CHANGE_LINES);
};
+ this.onChangeNewLineMode = function() {
+ this.$loop.schedule(this.CHANGE_TEXT);
+ this.$textLayer.$updateEolChar();
+ };
+
this.onChangeTabSize = function() {
this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER);
this.$textLayer.onChangeTabSize();
};
@@ -257,110 +310,167 @@
/**
* Triggers a full update of all the layers, for all the rows.
* @param {Boolean} force If `true`, forces the changes through
*
- *
+ *
**/
this.updateFull = function(force) {
- if (force){
+ if (force)
this.$renderChanges(this.CHANGE_FULL, true);
- }
- else {
+ else
this.$loop.schedule(this.CHANGE_FULL);
- }
};
/**
- *
+ *
* Updates the font size.
**/
this.updateFontSize = function() {
this.$textLayer.checkForSizeChanges();
};
+ this.$changes = 0;
+ this.$updateSizeAsync = function() {
+ if (this.$loop.pending)
+ this.$size.$dirty = true;
+ else
+ this.onResize();
+ };
/**
* [Triggers a resize of the editor.]{: #VirtualRenderer.onResize}
* @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed
* @param {Number} gutterWidth The width of the gutter in pixels
* @param {Number} width The width of the editor in pixels
* @param {Number} height The hiehgt of the editor, in pixels
*
- *
+ *
**/
this.onResize = function(force, gutterWidth, width, height) {
- var changes = this.CHANGE_SIZE;
- var size = this.$size;
-
if (this.resizing > 2)
return;
- else if (this.resizing > 1)
+ else if (this.resizing > 0)
this.resizing++;
else
this.resizing = force ? 1 : 0;
-
+ // `|| el.scrollHeight` is required for outosizing editors on ie
+ // where elements with clientHeight = 0 alsoe have clientWidth = 0
+ var el = this.container;
if (!height)
- height = dom.getInnerHeight(this.container);
- if (force || size.height != height) {
+ height = el.clientHeight || el.scrollHeight;
+ if (!width)
+ width = el.clientWidth || el.scrollWidth;
+ var changes = this.$updateCachedSize(force, gutterWidth, width, height);
+
+
+ if (!this.$size.scrollerHeight || (!width && !height))
+ return this.resizing = 0;
+
+ if (force)
+ this.$gutterLayer.$padding = null;
+
+ if (force)
+ this.$renderChanges(changes | this.$changes, true);
+ else
+ this.$loop.schedule(changes | this.$changes);
+
+ if (this.resizing)
+ this.resizing = 0;
+ // reset cached values on scrollbars, needs to be removed when switching to non-native scrollbars
+ // see https://github.com/ajaxorg/ace/issues/2195
+ this.scrollBarV.scrollLeft = this.scrollBarV.scrollTop = null;
+ };
+
+ this.$updateCachedSize = function(force, gutterWidth, width, height) {
+ height -= (this.$extraHeight || 0);
+ var changes = 0;
+ var size = this.$size;
+ var oldSize = {
+ width: size.width,
+ height: size.height,
+ scrollerHeight: size.scrollerHeight,
+ scrollerWidth: size.scrollerWidth
+ };
+ if (height && (force || size.height != height)) {
size.height = height;
+ changes |= this.CHANGE_SIZE;
- this.scroller.style.height = height + "px";
- size.scrollerHeight = this.scroller.clientHeight;
- this.scrollBar.setHeight(size.scrollerHeight);
+ size.scrollerHeight = size.height;
+ if (this.$horizScroll)
+ size.scrollerHeight -= this.scrollBarH.getHeight();
+
+ // this.scrollBarV.setHeight(size.scrollerHeight);
+ this.scrollBarV.element.style.bottom = this.scrollBarH.getHeight() + "px";
- if (this.session) {
- this.session.setScrollTop(this.getScrollTop());
- changes = changes | this.CHANGE_FULL;
- }
+ changes = changes | this.CHANGE_SCROLL;
}
- if (!width)
- width = dom.getInnerWidth(this.container);
- if (force || this.resizing > 1 || size.width != width) {
+ if (width && (force || size.width != width)) {
+ changes |= this.CHANGE_SIZE;
size.width = width;
-
- var gutterWidth = this.showGutter ? this.$gutter.offsetWidth : 0;
+
+ if (gutterWidth == null)
+ gutterWidth = this.$showGutter ? this.$gutter.offsetWidth : 0;
+
+ this.gutterWidth = gutterWidth;
+
+ this.scrollBarH.element.style.left =
this.scroller.style.left = gutterWidth + "px";
- size.scrollerWidth = Math.max(0, width - gutterWidth - this.scrollBar.getWidth());
- this.scroller.style.right = this.scrollBar.getWidth() + "px";
+ size.scrollerWidth = Math.max(0, width - gutterWidth - this.scrollBarV.getWidth());
+
+ this.scrollBarH.element.style.right =
+ this.scroller.style.right = this.scrollBarV.getWidth() + "px";
+ this.scroller.style.bottom = this.scrollBarH.getHeight() + "px";
+
+ // this.scrollBarH.element.style.setWidth(size.scrollerWidth);
- if (this.session.getUseWrapMode() && this.adjustWrapLimit() || force)
- changes = changes | this.CHANGE_FULL;
+ if (this.session && this.session.getUseWrapMode() && this.adjustWrapLimit() || force)
+ changes |= this.CHANGE_FULL;
}
-
- if (force)
- this.$renderChanges(changes, true);
- else
- this.$loop.schedule(changes);
- if (force)
- delete this.resizing;
+ size.$dirty = !width || !height;
+
+ if (changes)
+ this._signal("resize", oldSize);
+
+ return changes;
};
+ this.onGutterResize = function() {
+ var gutterWidth = this.$showGutter ? this.$gutter.offsetWidth : 0;
+ if (gutterWidth != this.gutterWidth)
+ this.$changes |= this.$updateCachedSize(true, gutterWidth, this.$size.width, this.$size.height);
+
+ if (this.session.getUseWrapMode() && this.adjustWrapLimit()) {
+ this.$loop.schedule(this.CHANGE_FULL);
+ } else if (this.$size.$dirty) {
+ this.$loop.schedule(this.CHANGE_FULL);
+ } else {
+ this.$computeLayerConfig();
+ this.$loop.schedule(this.CHANGE_MARKER);
+ }
+ };
+
/**
- *
* Adjusts the wrap limit, which is the number of characters that can fit within the width of the edit area on screen.
**/
this.adjustWrapLimit = function() {
var availableWidth = this.$size.scrollerWidth - this.$padding * 2;
var limit = Math.floor(availableWidth / this.characterWidth);
- return this.session.adjustWrapLimit(limit);
+ return this.session.adjustWrapLimit(limit, this.$showPrintMargin && this.$printMarginColumn);
};
/**
* Identifies whether you want to have an animated scroll or not.
* @param {Boolean} shouldAnimate Set to `true` to show animated scrolls
*
- *
- *
**/
this.setAnimatedScroll = function(shouldAnimate){
- this.$animatedScroll = shouldAnimate;
+ this.setOption("animatedScroll", shouldAnimate);
};
/**
- *
* Returns whether an animated scroll happens or not.
* @returns {Boolean}
**/
this.getAnimatedScroll = function() {
return this.$animatedScroll;
@@ -368,143 +478,109 @@
/**
* Identifies whether you want to show invisible characters or not.
* @param {Boolean} showInvisibles Set to `true` to show invisibles
*
- *
- *
**/
this.setShowInvisibles = function(showInvisibles) {
- if (this.$textLayer.setShowInvisibles(showInvisibles))
- this.$loop.schedule(this.CHANGE_TEXT);
+ this.setOption("showInvisibles", showInvisibles);
};
/**
- *
* Returns whether invisible characters are being shown or not.
* @returns {Boolean}
**/
this.getShowInvisibles = function() {
- return this.$textLayer.showInvisibles;
+ return this.getOption("showInvisibles");
};
-
this.getDisplayIndentGuides = function() {
- return this.$textLayer.displayIndentGuides;
+ return this.getOption("displayIndentGuides");
};
-
+
this.setDisplayIndentGuides = function(display) {
- if (this.$textLayer.setDisplayIndentGuides(display))
- this.$loop.schedule(this.CHANGE_TEXT);
+ this.setOption("displayIndentGuides", display);
};
-
- this.$showPrintMargin = true;
/**
* Identifies whether you want to show the print margin or not.
* @param {Boolean} showPrintMargin Set to `true` to show the print margin
*
- *
- *
**/
this.setShowPrintMargin = function(showPrintMargin) {
- this.$showPrintMargin = showPrintMargin;
- this.$updatePrintMargin();
+ this.setOption("showPrintMargin", showPrintMargin);
};
/**
* Returns whether the print margin is being shown or not.
* @returns {Boolean}
**/
this.getShowPrintMargin = function() {
- return this.$showPrintMargin;
+ return this.getOption("showPrintMargin");
};
-
- this.$printMarginColumn = 80;
-
/**
* Identifies whether you want to show the print margin column or not.
* @param {Boolean} showPrintMargin Set to `true` to show the print margin column
*
- *
- *
**/
this.setPrintMarginColumn = function(showPrintMargin) {
- this.$printMarginColumn = showPrintMargin;
- this.$updatePrintMargin();
+ this.setOption("printMarginColumn", showPrintMargin);
};
/**
- *
* Returns whether the print margin column is being shown or not.
* @returns {Boolean}
**/
this.getPrintMarginColumn = function() {
- return this.$printMarginColumn;
+ return this.getOption("printMarginColumn");
};
/**
- *
* Returns `true` if the gutter is being shown.
* @returns {Boolean}
**/
this.getShowGutter = function(){
- return this.showGutter;
+ return this.getOption("showGutter");
};
/**
* Identifies whether you want to show the gutter or not.
* @param {Boolean} show Set to `true` to show the gutter
*
- *
**/
this.setShowGutter = function(show){
- if(this.showGutter === show)
- return;
- this.$gutter.style.display = show ? "block" : "none";
- this.showGutter = show;
- this.onResize(true);
+ return this.setOption("showGutter", show);
};
this.getFadeFoldWidgets = function(){
- return dom.hasCssClass(this.$gutter, "ace_fade-fold-widgets");
+ return this.getOption("fadeFoldWidgets")
};
this.setFadeFoldWidgets = function(show) {
- if (show)
- dom.addCssClass(this.$gutter, "ace_fade-fold-widgets");
- else
- dom.removeCssClass(this.$gutter, "ace_fade-fold-widgets");
+ this.setOption("fadeFoldWidgets", show);
};
- this.$highlightGutterLine = false;
this.setHighlightGutterLine = function(shouldHighlight) {
- if (this.$highlightGutterLine == shouldHighlight)
- return;
- this.$highlightGutterLine = shouldHighlight;
-
- if (!this.$gutterLineHighlight) {
- this.$gutterLineHighlight = dom.createElement("div");
- this.$gutterLineHighlight.className = "ace_gutter-active-line";
- this.$gutter.appendChild(this.$gutterLineHighlight);
- return;
- }
-
- this.$gutterLineHighlight.style.display = shouldHighlight ? "" : "none";
- // if cursorlayer have never been updated there's nothing on screen to update
- if (this.$cursorLayer.$pixelPos)
- this.$updateGutterLineHighlight();
+ this.setOption("highlightGutterLine", shouldHighlight);
};
this.getHighlightGutterLine = function() {
- return this.$highlightGutterLine;
+ return this.getOption("highlightGutterLine");
};
this.$updateGutterLineHighlight = function() {
- this.$gutterLineHighlight.style.top = this.$cursorLayer.$pixelPos.top - this.layerConfig.offset + "px";
- this.$gutterLineHighlight.style.height = this.layerConfig.lineHeight + "px";
+ var pos = this.$cursorLayer.$pixelPos;
+ var height = this.layerConfig.lineHeight;
+ if (this.session.getUseWrapMode()) {
+ var cursor = this.session.selection.getCursor();
+ cursor.column = 0;
+ pos = this.$cursorLayer.getPixelPosition(cursor, true);
+ height *= this.session.getRowLength(cursor.row);
+ }
+ this.$gutterLineHighlight.style.top = pos.top - this.layerConfig.offset + "px";
+ this.$gutterLineHighlight.style.height = height + "px";
};
-
+
this.$updatePrintMargin = function() {
if (!this.$showPrintMargin && !this.$printMarginEl)
return;
if (!this.$printMarginEl) {
@@ -517,32 +593,35 @@
}
var style = this.$printMarginEl.style;
style.left = ((this.characterWidth * this.$printMarginColumn) + this.$padding) + "px";
style.visibility = this.$showPrintMargin ? "visible" : "hidden";
+
+ if (this.session && this.session.$wrap == -1)
+ this.adjustWrapLimit();
};
/**
- *
+ *
* Returns the root element containing this renderer.
* @returns {DOMElement}
**/
this.getContainerElement = function() {
return this.container;
};
/**
- *
+ *
* Returns the element that the mouse events are attached to
* @returns {DOMElement}
**/
this.getMouseEventTarget = function() {
return this.content;
};
/**
- *
+ *
* Returns the element to which the hidden text area is added.
* @returns {DOMElement}
**/
this.getTextAreaContainer = function() {
return this.container;
@@ -551,64 +630,69 @@
// move text input over the cursor
// this is required for iOS and IME
this.$moveTextAreaToCursor = function() {
if (!this.$keepTextAreaAtCursor)
return;
-
+ var config = this.layerConfig;
var posTop = this.$cursorLayer.$pixelPos.top;
var posLeft = this.$cursorLayer.$pixelPos.left;
- posTop -= this.layerConfig.offset;
+ posTop -= config.offset;
- if (posTop < 0 || posTop > this.layerConfig.height - this.lineHeight)
+ var style = this.textarea.style;
+ var h = this.lineHeight;
+ if (posTop < 0 || posTop > config.height - h) {
+ style.top = style.left = "0";
return;
+ }
var w = this.characterWidth;
- if (this.$composition)
- w += this.textarea.scrollWidth;
+ if (this.$composition) {
+ var val = this.textarea.value.replace(/^\x01+/, "");
+ w *= (this.session.$getStringScreenWidth(val)[0]+2);
+ h += 2;
+ }
posLeft -= this.scrollLeft;
if (posLeft > this.$size.scrollerWidth - w)
posLeft = this.$size.scrollerWidth - w;
- if (this.showGutter)
- posLeft += this.$gutterLayer.gutterWidth;
-
- this.textarea.style.height = this.lineHeight + "px";
- this.textarea.style.width = w + "px";
- this.textarea.style.left = posLeft + "px";
- this.textarea.style.top = posTop - 1 + "px";
+ posLeft += this.gutterWidth;
+ style.height = h + "px";
+ style.width = w + "px";
+ style.left = Math.min(posLeft, this.$size.scrollerWidth - w) + "px";
+ style.top = Math.min(posTop, this.$size.height - h) + "px";
};
/**
- *
+ *
* [Returns the index of the first visible row.]{: #VirtualRenderer.getFirstVisibleRow}
* @returns {Number}
**/
this.getFirstVisibleRow = function() {
return this.layerConfig.firstRow;
};
/**
- *
+ *
* Returns the index of the first fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
* @returns {Number}
**/
this.getFirstFullyVisibleRow = function() {
return this.layerConfig.firstRow + (this.layerConfig.offset === 0 ? 0 : 1);
};
/**
- *
+ *
* Returns the index of the last fully visible row. "Fully" here means that the characters in the row are not truncated; that the top and the bottom of the row are on the screen.
* @returns {Number}
**/
this.getLastFullyVisibleRow = function() {
var flint = Math.floor((this.layerConfig.height + this.layerConfig.offset) / this.layerConfig.lineHeight);
return this.layerConfig.firstRow - 1 + flint;
};
/**
- *
+ *
* [Returns the index of the last visible row.]{: #VirtualRenderer.getLastVisibleRow}
* @returns {Number}
**/
this.getLastVisibleRow = function() {
return this.layerConfig.lastRow;
@@ -617,164 +701,289 @@
this.$padding = null;
/**
* Sets the padding for all the layers.
* @param {Number} padding A new padding value (in pixels)
- *
- *
*
+ *
+ *
**/
this.setPadding = function(padding) {
this.$padding = padding;
this.$textLayer.setPadding(padding);
this.$cursorLayer.setPadding(padding);
this.$markerFront.setPadding(padding);
this.$markerBack.setPadding(padding);
this.$loop.schedule(this.CHANGE_FULL);
this.$updatePrintMargin();
};
+
+ this.setScrollMargin = function(top, bottom, left, right) {
+ var sm = this.scrollMargin;
+ sm.top = top|0;
+ sm.bottom = bottom|0;
+ sm.right = right|0;
+ sm.left = left|0;
+ sm.v = sm.top + sm.bottom;
+ sm.h = sm.left + sm.right;
+ if (sm.top && this.scrollTop <= 0 && this.session)
+ this.session.setScrollTop(-sm.top);
+ this.updateFull();
+ };
/**
- *
- * Returns whether the horizontal scrollbar is set to be always visible.
- * @returns {Boolean}
- **/
+ * Returns whether the horizontal scrollbar is set to be always visible.
+ * @returns {Boolean}
+ **/
this.getHScrollBarAlwaysVisible = function() {
- return this.$horizScrollAlwaysVisible;
+ return this.$hScrollBarAlwaysVisible;
};
/**
- * Identifies whether you want to show the horizontal scrollbar or not.
- * @param {Boolean} alwaysVisible Set to `true` to make the horizontal scroll bar visible
- *
- *
- **/
+ * Identifies whether you want to show the horizontal scrollbar or not.
+ * @param {Boolean} alwaysVisible Set to `true` to make the horizontal scroll bar visible
+ **/
this.setHScrollBarAlwaysVisible = function(alwaysVisible) {
- if (this.$horizScrollAlwaysVisible != alwaysVisible) {
- this.$horizScrollAlwaysVisible = alwaysVisible;
- if (!this.$horizScrollAlwaysVisible || !this.$horizScroll)
- this.$loop.schedule(this.CHANGE_SCROLL);
- }
+ this.setOption("hScrollBarAlwaysVisible", alwaysVisible);
};
+ /**
+ * Returns whether the horizontal scrollbar is set to be always visible.
+ * @returns {Boolean}
+ **/
+ this.getVScrollBarAlwaysVisible = function() {
+ return this.$vScrollBarAlwaysVisible;
+ };
- this.$updateScrollBar = function() {
- this.scrollBar.setInnerHeight(this.layerConfig.maxHeight);
- this.scrollBar.setScrollTop(this.scrollTop);
+ /**
+ * Identifies whether you want to show the horizontal scrollbar or not.
+ * @param {Boolean} alwaysVisible Set to `true` to make the horizontal scroll bar visible
+ **/
+ this.setVScrollBarAlwaysVisible = function(alwaysVisible) {
+ this.setOption("vScrollBarAlwaysVisible", alwaysVisible);
};
- this.$renderChanges = function(changes, force) {
- if (!force && (!changes || !this.session || !this.container.offsetWidth))
- return;
+ this.$updateScrollBarV = function() {
+ var scrollHeight = this.layerConfig.maxHeight;
+ var scrollerHeight = this.$size.scrollerHeight;
+ if (!this.$maxLines && this.$scrollPastEnd) {
+ scrollHeight -= (scrollerHeight - this.lineHeight) * this.$scrollPastEnd;
+ if (this.scrollTop > scrollHeight - scrollerHeight) {
+ scrollHeight = this.scrollTop + scrollerHeight;
+ this.scrollBarV.scrollTop = null;
+ }
+ }
+ this.scrollBarV.setScrollHeight(scrollHeight + this.scrollMargin.v);
+ this.scrollBarV.setScrollTop(this.scrollTop + this.scrollMargin.top);
+ };
+ this.$updateScrollBarH = function() {
+ this.scrollBarH.setScrollWidth(this.layerConfig.width + 2 * this.$padding + this.scrollMargin.h);
+ this.scrollBarH.setScrollLeft(this.scrollLeft + this.scrollMargin.left);
+ };
+
+ this.$frozen = false;
+ this.freeze = function() {
+ this.$frozen = true;
+ };
+
+ this.unfreeze = function() {
+ this.$frozen = false;
+ };
+ this.$renderChanges = function(changes, force) {
+ if (this.$changes) {
+ changes |= this.$changes;
+ this.$changes = 0;
+ }
+ if ((!this.session || !this.container.offsetWidth || this.$frozen) || (!changes && !force)) {
+ this.$changes |= changes;
+ return;
+ }
+ if (this.$size.$dirty) {
+ this.$changes |= changes;
+ return this.onResize(true);
+ }
+ if (!this.lineHeight) {
+ this.$textLayer.checkForSizeChanges();
+ }
+ // this.$logChanges(changes);
+
+ this._signal("beforeRender");
+ var config = this.layerConfig;
// text, scrolling and resize changes can cause the view port size to change
if (changes & this.CHANGE_FULL ||
changes & this.CHANGE_SIZE ||
changes & this.CHANGE_TEXT ||
changes & this.CHANGE_LINES ||
- changes & this.CHANGE_SCROLL
- )
- this.$computeLayerConfig();
-
+ changes & this.CHANGE_SCROLL ||
+ changes & this.CHANGE_H_SCROLL
+ ) {
+ changes |= this.$computeLayerConfig();
+ // If a change is made offscreen and wrapMode is on, then the onscreen
+ // lines may have been pushed down. If so, the first screen row will not
+ // have changed, but the first actual row will. In that case, adjust
+ // scrollTop so that the cursor and onscreen content stays in the same place.
+ // TODO: find a better way to handle this, that works non wrapped case and doesn't compute layerConfig twice
+ if (config.firstRow != this.layerConfig.firstRow && config.firstRowScreen == this.layerConfig.firstRowScreen) {
+ var st = this.scrollTop + (config.firstRow - this.layerConfig.firstRow) * this.lineHeight;
+ if (st > 0) {
+ // this check is needed as a workaround for the documentToScreenRow returning -1 if document.length == 0
+ this.scrollTop = st;
+ changes = changes | this.CHANGE_SCROLL;
+ changes |= this.$computeLayerConfig();
+ }
+ }
+ config = this.layerConfig;
+ // update scrollbar first to not lose scroll position when gutter calls resize
+ this.$updateScrollBarV();
+ if (changes & this.CHANGE_H_SCROLL)
+ this.$updateScrollBarH();
+ this.$gutterLayer.element.style.marginTop = (-config.offset) + "px";
+ this.content.style.marginTop = (-config.offset) + "px";
+ this.content.style.width = config.width + 2 * this.$padding + "px";
+ this.content.style.height = config.minHeight + "px";
+ }
+
// horizontal scrolling
if (changes & this.CHANGE_H_SCROLL) {
- this.scroller.scrollLeft = this.scrollLeft;
-
- // read the value after writing it since the value might get clipped
- var scrollLeft = this.scroller.scrollLeft;
- this.scrollLeft = scrollLeft;
- this.session.setScrollLeft(scrollLeft);
-
- this.scroller.className = this.scrollLeft == 0 ? "ace_scroller" : "ace_scroller ace_scroll-left";
+ this.content.style.marginLeft = -this.scrollLeft + "px";
+ this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller" : "ace_scroller ace_scroll-left";
}
// full
if (changes & this.CHANGE_FULL) {
- this.$textLayer.checkForSizeChanges();
- // update scrollbar first to not lose scroll position when gutter calls resize
- this.$updateScrollBar();
- this.$textLayer.update(this.layerConfig);
- if (this.showGutter)
- this.$gutterLayer.update(this.layerConfig);
- this.$markerBack.update(this.layerConfig);
- this.$markerFront.update(this.layerConfig);
- this.$cursorLayer.update(this.layerConfig);
+ this.$textLayer.update(config);
+ if (this.$showGutter)
+ this.$gutterLayer.update(config);
+ this.$markerBack.update(config);
+ this.$markerFront.update(config);
+ this.$cursorLayer.update(config);
this.$moveTextAreaToCursor();
this.$highlightGutterLine && this.$updateGutterLineHighlight();
+ this._signal("afterRender");
return;
}
// scrolling
if (changes & this.CHANGE_SCROLL) {
- this.$updateScrollBar();
if (changes & this.CHANGE_TEXT || changes & this.CHANGE_LINES)
- this.$textLayer.update(this.layerConfig);
+ this.$textLayer.update(config);
else
- this.$textLayer.scrollLines(this.layerConfig);
+ this.$textLayer.scrollLines(config);
- if (this.showGutter)
- this.$gutterLayer.update(this.layerConfig);
- this.$markerBack.update(this.layerConfig);
- this.$markerFront.update(this.layerConfig);
- this.$cursorLayer.update(this.layerConfig);
- this.$moveTextAreaToCursor();
+ if (this.$showGutter)
+ this.$gutterLayer.update(config);
+ this.$markerBack.update(config);
+ this.$markerFront.update(config);
+ this.$cursorLayer.update(config);
this.$highlightGutterLine && this.$updateGutterLineHighlight();
+ this.$moveTextAreaToCursor();
+ this._signal("afterRender");
return;
}
if (changes & this.CHANGE_TEXT) {
- this.$textLayer.update(this.layerConfig);
- if (this.showGutter)
- this.$gutterLayer.update(this.layerConfig);
+ this.$textLayer.update(config);
+ if (this.$showGutter)
+ this.$gutterLayer.update(config);
}
else if (changes & this.CHANGE_LINES) {
- if (this.$updateLines() || (changes & this.CHANGE_GUTTER) && this.showGutter)
- this.$gutterLayer.update(this.layerConfig);
+ if (this.$updateLines() || (changes & this.CHANGE_GUTTER) && this.$showGutter)
+ this.$gutterLayer.update(config);
}
else if (changes & this.CHANGE_TEXT || changes & this.CHANGE_GUTTER) {
- if (this.showGutter)
- this.$gutterLayer.update(this.layerConfig);
+ if (this.$showGutter)
+ this.$gutterLayer.update(config);
}
if (changes & this.CHANGE_CURSOR) {
- this.$cursorLayer.update(this.layerConfig);
+ this.$cursorLayer.update(config);
this.$moveTextAreaToCursor();
this.$highlightGutterLine && this.$updateGutterLineHighlight();
}
if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT)) {
- this.$markerFront.update(this.layerConfig);
+ this.$markerFront.update(config);
}
if (changes & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK)) {
- this.$markerBack.update(this.layerConfig);
+ this.$markerBack.update(config);
}
- if (changes & this.CHANGE_SIZE)
- this.$updateScrollBar();
+ this._signal("afterRender");
};
+
+ this.$autosize = function() {
+ var height = this.session.getScreenLength() * this.lineHeight;
+ var maxHeight = this.$maxLines * this.lineHeight;
+ var desiredHeight = Math.max(
+ (this.$minLines||1) * this.lineHeight,
+ Math.min(maxHeight, height)
+ ) + this.scrollMargin.v + (this.$extraHeight || 0);
+ var vScroll = height > maxHeight;
+
+ if (desiredHeight != this.desiredHeight ||
+ this.$size.height != this.desiredHeight || vScroll != this.$vScroll) {
+ if (vScroll != this.$vScroll) {
+ this.$vScroll = vScroll;
+ this.scrollBarV.setVisible(vScroll);
+ }
+
+ var w = this.container.clientWidth;
+ this.container.style.height = desiredHeight + "px";
+ this.$updateCachedSize(true, this.$gutterWidth, w, desiredHeight);
+ // this.$loop.changes = 0;
+ this.desiredHeight = desiredHeight;
+
+ this._signal("autosize");
+ }
+ };
+
this.$computeLayerConfig = function() {
+ if (this.$maxLines && this.lineHeight > 1)
+ this.$autosize();
+
var session = this.session;
+ var size = this.$size;
+
+ var hideScrollbars = size.height <= 2 * this.lineHeight;
+ var screenLines = this.session.getScreenLength();
+ var maxHeight = screenLines * this.lineHeight;
var offset = this.scrollTop % this.lineHeight;
- var minHeight = this.$size.scrollerHeight + this.lineHeight;
+ var minHeight = size.scrollerHeight + this.lineHeight;
var longestLine = this.$getLongestLine();
+
+ var horizScroll = !hideScrollbars && (this.$hScrollBarAlwaysVisible ||
+ size.scrollerWidth - longestLine - 2 * this.$padding < 0);
- var horizScroll = this.$horizScrollAlwaysVisible || this.$size.scrollerWidth - longestLine < 0;
- var horizScrollChanged = this.$horizScroll !== horizScroll;
- this.$horizScroll = horizScroll;
- if (horizScrollChanged) {
- this.scroller.style.overflowX = horizScroll ? "scroll" : "hidden";
- // when we hide scrollbar scroll event isn't emited
- // leaving session with wrong scrollLeft value
- if (!horizScroll)
- this.session.setScrollLeft(0);
+ var hScrollChanged = this.$horizScroll !== horizScroll;
+ if (hScrollChanged) {
+ this.$horizScroll = horizScroll;
+ this.scrollBarH.setVisible(horizScroll);
}
- var maxHeight = this.session.getScreenLength() * this.lineHeight;
- this.session.setScrollTop(Math.max(0, Math.min(this.scrollTop, maxHeight - this.$size.scrollerHeight)));
+
+ var scrollPastEnd = !this.$maxLines && this.$scrollPastEnd
+ ? (size.scrollerHeight - this.lineHeight) * this.$scrollPastEnd
+ : 0;
+ maxHeight += scrollPastEnd;
+
+ this.session.setScrollTop(Math.max(-this.scrollMargin.top,
+ Math.min(this.scrollTop, maxHeight - size.scrollerHeight + this.scrollMargin.bottom)));
+ this.session.setScrollLeft(Math.max(-this.scrollMargin.left, Math.min(this.scrollLeft,
+ longestLine + 2 * this.$padding - size.scrollerWidth + this.scrollMargin.right)));
+
+ var vScroll = !hideScrollbars && (this.$vScrollBarAlwaysVisible ||
+ size.scrollerHeight - maxHeight + scrollPastEnd < 0 || this.scrollTop);
+ var vScrollChanged = this.$vScroll !== vScroll;
+ if (vScrollChanged) {
+ this.$vScroll = vScroll;
+ this.scrollBarV.setVisible(vScroll);
+ }
+
var lineCount = Math.ceil(minHeight / this.lineHeight) - 1;
var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight));
var lastRow = firstRow + lineCount;
// Map lines on the screen to lines in the document.
@@ -791,15 +1000,27 @@
firstRowScreen = session.documentToScreenRow(firstRow, 0);
firstRowHeight = session.getRowLength(firstRow) * lineHeight;
lastRow = Math.min(session.screenToDocumentRow(lastRow, 0), session.getLength() - 1);
- minHeight = this.$size.scrollerHeight + session.getRowLength(lastRow) * lineHeight +
+ minHeight = size.scrollerHeight + session.getRowLength(lastRow) * lineHeight +
firstRowHeight;
offset = this.scrollTop - firstRowScreen * lineHeight;
+ var changes = 0;
+ if (this.layerConfig.width != longestLine)
+ changes = this.CHANGE_H_SCROLL;
+ // Horizontal scrollbar visibility may have changed, which changes
+ // the client height of the scroller
+ if (hScrollChanged || vScrollChanged) {
+ changes = this.$updateCachedSize(true, this.gutterWidth, size.width, size.height);
+ this._signal("scrollbarVisibilityChanged");
+ if (vScrollChanged)
+ longestLine = this.$getLongestLine();
+ }
+
this.layerConfig = {
width : longestLine,
padding : this.$padding,
firstRow : firstRow,
firstRowScreen: firstRowScreen,
@@ -807,25 +1028,18 @@
lineHeight : lineHeight,
characterWidth : this.characterWidth,
minHeight : minHeight,
maxHeight : maxHeight,
offset : offset,
+ gutterOffset : Math.max(0, Math.ceil((offset + size.height - size.scrollerHeight) / lineHeight)),
height : this.$size.scrollerHeight
};
// For debugging.
// console.log(JSON.stringify(this.layerConfig));
- this.$gutterLayer.element.style.marginTop = (-offset) + "px";
- this.content.style.marginTop = (-offset) + "px";
- this.content.style.width = longestLine + 2 * this.$padding + "px";
- this.content.style.height = minHeight + "px";
-
- // Horizontal scrollbar visibility may have changed, which changes
- // the client height of the scroller
- if (horizScrollChanged)
- this.onResize(true);
+ return changes;
};
this.$updateLines = function() {
var firstRow = this.$changedLines.firstRow;
var lastRow = this.$changedLines.lastRow;
@@ -836,11 +1050,11 @@
if (firstRow > layerConfig.lastRow + 1) { return; }
if (lastRow < layerConfig.firstRow) { return; }
// if the last row is unknown -> redraw everything
if (lastRow === Infinity) {
- if (this.showGutter)
+ if (this.$showGutter)
this.$gutterLayer.update(layerConfig);
this.$textLayer.update(layerConfig);
return;
}
@@ -849,89 +1063,89 @@
return true;
};
this.$getLongestLine = function() {
var charCount = this.session.getScreenWidth();
- if (this.$textLayer.showInvisibles)
+ if (this.showInvisibles && !this.session.$useWrapMode)
charCount += 1;
return Math.max(this.$size.scrollerWidth - 2 * this.$padding, Math.round(charCount * this.characterWidth));
};
/**
- *
+ *
* Schedules an update to all the front markers in the document.
**/
this.updateFrontMarkers = function() {
this.$markerFront.setMarkers(this.session.getMarkers(true));
this.$loop.schedule(this.CHANGE_MARKER_FRONT);
};
/**
- *
+ *
* Schedules an update to all the back markers in the document.
**/
this.updateBackMarkers = function() {
this.$markerBack.setMarkers(this.session.getMarkers());
this.$loop.schedule(this.CHANGE_MARKER_BACK);
};
- /**
- *
+ /**
+ *
* Deprecated; (moved to [[EditSession]])
* @deprecated
**/
this.addGutterDecoration = function(row, className){
this.$gutterLayer.addGutterDecoration(row, className);
};
- /**
+ /**
* Deprecated; (moved to [[EditSession]])
* @deprecated
**/
this.removeGutterDecoration = function(row, className){
this.$gutterLayer.removeGutterDecoration(row, className);
};
/**
- *
+ *
* Redraw breakpoints.
**/
this.updateBreakpoints = function(rows) {
this.$loop.schedule(this.CHANGE_GUTTER);
};
/**
- *
+ *
* Sets annotations for the gutter.
* @param {Array} annotations An array containing annotations
*
- *
+ *
**/
this.setAnnotations = function(annotations) {
this.$gutterLayer.setAnnotations(annotations);
this.$loop.schedule(this.CHANGE_GUTTER);
};
/**
- *
+ *
* Updates the cursor icon.
**/
this.updateCursor = function() {
this.$loop.schedule(this.CHANGE_CURSOR);
};
/**
- *
+ *
* Hides the cursor icon.
**/
this.hideCursor = function() {
this.$cursorLayer.hideCursor();
};
/**
- *
+ *
* Shows the cursor icon.
**/
this.showCursor = function() {
this.$cursorLayer.showCursor();
};
@@ -941,85 +1155,94 @@
this.scrollCursorIntoView(anchor, offset);
this.scrollCursorIntoView(lead, offset);
};
/**
- *
+ *
* Scrolls the cursor into the first visibile area of the editor
**/
- this.scrollCursorIntoView = function(cursor, offset) {
+ this.scrollCursorIntoView = function(cursor, offset, $viewMargin) {
// the editor is not visible
if (this.$size.scrollerHeight === 0)
return;
var pos = this.$cursorLayer.getPixelPosition(cursor);
var left = pos.left;
var top = pos.top;
-
- if (this.scrollTop > top) {
+
+ var topMargin = $viewMargin && $viewMargin.top || 0;
+ var bottomMargin = $viewMargin && $viewMargin.bottom || 0;
+
+ var scrollTop = this.$scrollAnimation ? this.session.getScrollTop() : this.scrollTop;
+
+ if (scrollTop + topMargin > top) {
if (offset)
top -= offset * this.$size.scrollerHeight;
+ if (top === 0)
+ top = -this.scrollMargin.top;
this.session.setScrollTop(top);
- } else if (this.scrollTop + this.$size.scrollerHeight < top + this.lineHeight) {
+ } else if (scrollTop + this.$size.scrollerHeight - bottomMargin < top + this.lineHeight) {
if (offset)
top += offset * this.$size.scrollerHeight;
this.session.setScrollTop(top + this.lineHeight - this.$size.scrollerHeight);
}
var scrollLeft = this.scrollLeft;
if (scrollLeft > left) {
if (left < this.$padding + 2 * this.layerConfig.characterWidth)
- left = 0;
+ left = -this.scrollMargin.left;
this.session.setScrollLeft(left);
} else if (scrollLeft + this.$size.scrollerWidth < left + this.characterWidth) {
this.session.setScrollLeft(Math.round(left + this.characterWidth - this.$size.scrollerWidth));
+ } else if (scrollLeft <= this.$padding && left - scrollLeft < this.characterWidth) {
+ this.session.setScrollLeft(0);
}
};
- /**
+ /**
* {:EditSession.getScrollTop}
* @related EditSession.getScrollTop
* @returns {Number}
**/
this.getScrollTop = function() {
return this.session.getScrollTop();
};
- /**
+ /**
* {:EditSession.getScrollLeft}
* @related EditSession.getScrollLeft
* @returns {Number}
**/
this.getScrollLeft = function() {
return this.session.getScrollLeft();
};
/**
- *
+ *
* Returns the first visible row, regardless of whether it's fully visible or not.
* @returns {Number}
**/
this.getScrollTopRow = function() {
return this.scrollTop / this.lineHeight;
};
/**
- *
+ *
* Returns the last visible row, regardless of whether it's fully visible or not.
* @returns {Number}
**/
this.getScrollBottomRow = function() {
return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1);
};
- /**
+ /**
* Gracefully scrolls from the top of the editor to the row indicated.
* @param {Number} row A row id
*
- *
+ *
* @related EditSession.setScrollTop
**/
this.scrollToRow = function(row) {
this.session.setScrollTop(row * this.lineHeight);
};
@@ -1057,11 +1280,11 @@
* @param {Number} line A line number
* @param {Boolean} center If `true`, centers the editor the to indicated line
* @param {Boolean} animate If `true` animates scrolling
* @param {Function} callback Function to be called after the animation has finished
*
- *
+ *
**/
this.scrollToLine = function(line, center, animate, callback) {
var pos = this.$cursorLayer.getPixelPosition({row: line, column: 0});
var offset = pos.top;
if (center)
@@ -1073,42 +1296,56 @@
this.animateScrolling(initialScroll, callback);
};
this.animateScrolling = function(fromValue, callback) {
var toValue = this.scrollTop;
- if (this.$animatedScroll && Math.abs(fromValue - toValue) < 100000) {
- var _self = this;
- var steps = _self.$calcSteps(fromValue, toValue);
- this.$inScrollAnimation = true;
+ if (!this.$animatedScroll)
+ return;
+ var _self = this;
+
+ if (fromValue == toValue)
+ return;
+
+ if (this.$scrollAnimation) {
+ var oldSteps = this.$scrollAnimation.steps;
+ if (oldSteps.length) {
+ fromValue = oldSteps[0];
+ if (fromValue == toValue)
+ return;
+ }
+ }
+
+ var steps = _self.$calcSteps(fromValue, toValue);
+ this.$scrollAnimation = {from: fromValue, to: toValue, steps: steps};
- clearInterval(this.$timer);
+ clearInterval(this.$timer);
- _self.session.setScrollTop(steps.shift());
- this.$timer = setInterval(function() {
- if (steps.length) {
- _self.session.setScrollTop(steps.shift());
- // trick session to think it's already scrolled to not loose toValue
- _self.session.$scrollTop = toValue;
- } else if (toValue != null) {
- _self.session.$scrollTop = -1;
- _self.session.setScrollTop(toValue);
- toValue = null;
- } else {
- // do this on separate step to not get spurious scroll event from scrollbar
- _self.$timer = clearInterval(_self.$timer);
- _self.$inScrollAnimation = false;
- callback && callback();
- }
- }, 10);
- }
+ _self.session.setScrollTop(steps.shift());
+ // trick session to think it's already scrolled to not loose toValue
+ _self.session.$scrollTop = toValue;
+ this.$timer = setInterval(function() {
+ if (steps.length) {
+ _self.session.setScrollTop(steps.shift());
+ _self.session.$scrollTop = toValue;
+ } else if (toValue != null) {
+ _self.session.$scrollTop = -1;
+ _self.session.setScrollTop(toValue);
+ toValue = null;
+ } else {
+ // do this on separate step to not get spurious scroll event from scrollbar
+ _self.$timer = clearInterval(_self.$timer);
+ _self.$scrollAnimation = null;
+ callback && callback();
+ }
+ }, 10);
};
/**
* Scrolls the editor to the y pixel indicated.
* @param {Number} scrollTop The position to scroll to
*
- *
+ *
* @returns {Number}
**/
this.scrollToY = function(scrollTop) {
// after calling scrollBar.setScrollTop
// scrollbar sends us event with same scrollTop. ignore it
@@ -1120,28 +1357,33 @@
/**
* Scrolls the editor across the x-axis to the pixel indicated.
* @param {Number} scrollLeft The position to scroll to
*
- *
+ *
* @returns {Number}
**/
this.scrollToX = function(scrollLeft) {
- if (scrollLeft < 0)
- scrollLeft = 0;
-
if (this.scrollLeft !== scrollLeft)
this.scrollLeft = scrollLeft;
this.$loop.schedule(this.CHANGE_H_SCROLL);
};
/**
* Scrolls the editor across both x- and y-axes.
+ * @param {Number} x The x value to scroll to
+ * @param {Number} y The y value to scroll to
+ **/
+ this.scrollTo = function(x, y) {
+ this.session.setScrollTop(y);
+ this.session.setScrollLeft(y);
+ };
+
+ /**
+ * Scrolls the editor across both x- and y-axes.
* @param {Number} deltaX The x value to scroll by
* @param {Number} deltaY The y value to scroll by
- *
- *
**/
this.scrollBy = function(deltaX, deltaY) {
deltaY && this.session.setScrollTop(this.session.getScrollTop() + deltaY);
deltaX && this.session.setScrollLeft(this.session.getScrollLeft() + deltaX);
};
@@ -1149,19 +1391,24 @@
/**
* Returns `true` if you can still scroll by either parameter; in other words, you haven't reached the end of the file or line.
* @param {Number} deltaX The x value to scroll by
* @param {Number} deltaY The y value to scroll by
*
- *
+ *
* @returns {Boolean}
**/
this.isScrollableBy = function(deltaX, deltaY) {
- if (deltaY < 0 && this.session.getScrollTop() > 0)
+ if (deltaY < 0 && this.session.getScrollTop() >= 1 - this.scrollMargin.top)
return true;
- if (deltaY > 0 && this.session.getScrollTop() + this.$size.scrollerHeight < this.layerConfig.maxHeight)
+ if (deltaY > 0 && this.session.getScrollTop() + this.$size.scrollerHeight
+ - this.layerConfig.maxHeight < -1 + this.scrollMargin.bottom)
return true;
- // todo: handle horizontal scrolling
+ if (deltaX < 0 && this.session.getScrollLeft() >= 1 - this.scrollMargin.left)
+ return true;
+ if (deltaX > 0 && this.session.getScrollLeft() + this.$size.scrollerWidth
+ - this.layerConfig.width < -1 + this.scrollMargin.right)
+ return true;
};
this.pixelToScreenCoordinates = function(x, y) {
var canvasPos = this.scroller.getBoundingClientRect();
@@ -1176,24 +1423,23 @@
var canvasPos = this.scroller.getBoundingClientRect();
var col = Math.round(
(x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth
);
- var row = Math.floor(
- (y + this.scrollTop - canvasPos.top) / this.lineHeight
- );
+ var row = (y + this.scrollTop - canvasPos.top) / this.lineHeight;
+
return this.session.screenToDocumentPosition(row, Math.max(col, 0));
};
/**
* Returns an object containing the `pageX` and `pageY` coordinates of the document position.
* @param {Number} row The document row position
* @param {Number} column The document column position
*
- *
*
+ *
* @returns {Object}
**/
this.textToScreenCoordinates = function(row, column) {
var canvasPos = this.scroller.getBoundingClientRect();
var pos = this.session.documentToScreenPosition(row, column);
@@ -1206,30 +1452,30 @@
pageY: canvasPos.top + y - this.scrollTop
};
};
/**
- *
- * Focuses the current container.
- **/
+ *
+ * Focuses the current container.
+ **/
this.visualizeFocus = function() {
dom.addCssClass(this.container, "ace_focus");
};
/**
- *
- * Blurs the current container.
- **/
+ *
+ * Blurs the current container.
+ **/
this.visualizeBlur = function() {
dom.removeCssClass(this.container, "ace_focus");
};
- /**
- * @param {Number} position
- *
- * @private
- **/
+ /**
+ * @param {Number} position
+ *
+ * @private
+ **/
this.showComposition = function(position) {
if (!this.$composition)
this.$composition = {
keepTextAreaAtCursor: this.$keepTextAreaAtCursor,
cssText: this.textarea.style.cssText
@@ -1240,140 +1486,279 @@
this.textarea.style.cssText = "";
this.$moveTextAreaToCursor();
};
/**
- * @param {String} text A string of text to use
- *
- * Sets the inner text of the current composition to `text`.
- **/
+ * @param {String} text A string of text to use
+ *
+ * Sets the inner text of the current composition to `text`.
+ **/
this.setCompositionText = function(text) {
this.$moveTextAreaToCursor();
};
/**
- *
- * Hides the current composition.
- **/
+ *
+ * Hides the current composition.
+ **/
this.hideComposition = function() {
if (!this.$composition)
return;
dom.removeCssClass(this.textarea, "ace_composition");
this.$keepTextAreaAtCursor = this.$composition.keepTextAreaAtCursor;
this.textarea.style.cssText = this.$composition.cssText;
this.$composition = null;
};
- this._loadTheme = function(name, callback) {
- if (!config.get("packaged"))
- return callback();
-
- net.loadScript(config.moduleUrl(name, "theme"), callback);
- };
-
/**
- * [Sets a new theme for the editor. `theme` should exist, and be a directory path, like `ace/theme/textmate`.]{: #VirtualRenderer.setTheme}
- * @param {String} theme The path to a theme
- *
- *
- **/
- this.setTheme = function(theme) {
+ * [Sets a new theme for the editor. `theme` should exist, and be a directory path, like `ace/theme/textmate`.]{: #VirtualRenderer.setTheme}
+ * @param {String} theme The path to a theme
+ * @param {Function} cb optional callback
+ *
+ **/
+ this.setTheme = function(theme, cb) {
var _self = this;
+ this.$themeId = theme;
+ _self._dispatchEvent('themeChange',{theme:theme});
- this.$themeValue = theme;
if (!theme || typeof theme == "string") {
- var moduleName = theme || "ace/theme/textmate";
-
- var module;
- try {
- module = require(moduleName);
- } catch (e) {};
- if (module)
- return afterLoad(module);
-
- _self._loadTheme(moduleName, function() {
- require([moduleName], function(module) {
- if (_self.$themeValue !== theme)
- return;
-
- afterLoad(module);
- });
- });
+ var moduleName = theme || this.$options.theme.initialValue;
+ config.loadModule(["theme", moduleName], afterLoad);
} else {
afterLoad(theme);
}
- function afterLoad(theme) {
+ function afterLoad(module) {
+ if (_self.$themeId != theme)
+ return cb && cb();
+ if (!module.cssClass)
+ return;
dom.importCssString(
- theme.cssText,
- theme.cssClass,
+ module.cssText,
+ module.cssClass,
_self.container.ownerDocument
);
if (_self.theme)
dom.removeCssClass(_self.container, _self.theme.cssClass);
- // this is kept only for backwards compatibility
- _self.$theme = theme.cssClass;
-
- _self.theme = theme;
- dom.addCssClass(_self.container, theme.cssClass);
- dom.setCssClass(_self.container, "ace_dark", theme.isDark);
-
- var padding = theme.padding || 4;
+ var padding = "padding" in module ? module.padding
+ : "padding" in (_self.theme || {}) ? 4 : _self.$padding;
if (_self.$padding && padding != _self.$padding)
_self.setPadding(padding);
+
+ // this is kept only for backwards compatibility
+ _self.$theme = module.cssClass;
+ _self.theme = module;
+ dom.addCssClass(_self.container, module.cssClass);
+ dom.setCssClass(_self.container, "ace_dark", module.isDark);
+
// force re-measure of the gutter width
if (_self.$size) {
_self.$size.width = 0;
- _self.onResize();
+ _self.$updateSizeAsync();
}
+
+ _self._dispatchEvent('themeLoaded', {theme:module});
+ cb && cb();
}
};
/**
- * [Returns the path of the current theme.]{: #VirtualRenderer.getTheme}
- * @returns {String}
- **/
+ * [Returns the path of the current theme.]{: #VirtualRenderer.getTheme}
+ * @returns {String}
+ **/
this.getTheme = function() {
- return this.$themeValue;
+ return this.$themeId;
};
// Methods allows to add / remove CSS classnames to the editor element.
// This feature can be used by plug-ins to provide a visual indication of
// a certain mode that editor is in.
/**
- * [Adds a new class, `style`, to the editor.]{: #VirtualRenderer.setStyle}
- * @param {String} style A class name
- *
- *
- **/
- this.setStyle = function setStyle(style, include) {
- dom.setCssClass(this.container, style, include != false);
+ * [Adds a new class, `style`, to the editor.]{: #VirtualRenderer.setStyle}
+ * @param {String} style A class name
+ *
+ **/
+ this.setStyle = function(style, include) {
+ dom.setCssClass(this.container, style, include !== false);
};
/**
- * [Removes the class `style` from the editor.]{: #VirtualRenderer.unsetStyle}
- * @param {String} style A class name
- *
- *
- **/
- this.unsetStyle = function unsetStyle(style) {
+ * [Removes the class `style` from the editor.]{: #VirtualRenderer.unsetStyle}
+ * @param {String} style A class name
+ *
+ **/
+ this.unsetStyle = function(style) {
dom.removeCssClass(this.container, style);
};
+
+ this.setCursorStyle = function(style) {
+ if (this.scroller.style.cursor != style)
+ this.scroller.style.cursor = style;
+ };
/**
- *
- * Destroys the text and cursor layers for this renderer.
- **/
+ * @param {String} cursorStyle A css cursor style
+ *
+ **/
+ this.setMouseCursor = function(cursorStyle) {
+ this.scroller.style.cursor = cursorStyle;
+ };
+
+ /**
+ * Destroys the text and cursor layers for this renderer.
+ **/
this.destroy = function() {
this.$textLayer.destroy();
this.$cursorLayer.destroy();
};
}).call(VirtualRenderer.prototype);
+
+
+config.defineOptions(VirtualRenderer.prototype, "renderer", {
+ animatedScroll: {initialValue: false},
+ showInvisibles: {
+ set: function(value) {
+ if (this.$textLayer.setShowInvisibles(value))
+ this.$loop.schedule(this.CHANGE_TEXT);
+ },
+ initialValue: false
+ },
+ showPrintMargin: {
+ set: function() { this.$updatePrintMargin(); },
+ initialValue: true
+ },
+ printMarginColumn: {
+ set: function() { this.$updatePrintMargin(); },
+ initialValue: 80
+ },
+ printMargin: {
+ set: function(val) {
+ if (typeof val == "number")
+ this.$printMarginColumn = val;
+ this.$showPrintMargin = !!val;
+ this.$updatePrintMargin();
+ },
+ get: function() {
+ return this.$showPrintMargin && this.$printMarginColumn;
+ }
+ },
+ showGutter: {
+ set: function(show){
+ this.$gutter.style.display = show ? "block" : "none";
+ this.$loop.schedule(this.CHANGE_FULL);
+ this.onGutterResize();
+ },
+ initialValue: true
+ },
+ fadeFoldWidgets: {
+ set: function(show) {
+ dom.setCssClass(this.$gutter, "ace_fade-fold-widgets", show);
+ },
+ initialValue: false
+ },
+ showFoldWidgets: {
+ set: function(show) {this.$gutterLayer.setShowFoldWidgets(show)},
+ initialValue: true
+ },
+ showLineNumbers: {
+ set: function(show) {
+ this.$gutterLayer.setShowLineNumbers(show);
+ this.$loop.schedule(this.CHANGE_GUTTER);
+ },
+ initialValue: true
+ },
+ displayIndentGuides: {
+ set: function(show) {
+ if (this.$textLayer.setDisplayIndentGuides(show))
+ this.$loop.schedule(this.CHANGE_TEXT);
+ },
+ initialValue: true
+ },
+ highlightGutterLine: {
+ set: function(shouldHighlight) {
+ if (!this.$gutterLineHighlight) {
+ this.$gutterLineHighlight = dom.createElement("div");
+ this.$gutterLineHighlight.className = "ace_gutter-active-line";
+ this.$gutter.appendChild(this.$gutterLineHighlight);
+ return;
+ }
+
+ this.$gutterLineHighlight.style.display = shouldHighlight ? "" : "none";
+ // if cursorlayer have never been updated there's nothing on screen to update
+ if (this.$cursorLayer.$pixelPos)
+ this.$updateGutterLineHighlight();
+ },
+ initialValue: false,
+ value: true
+ },
+ hScrollBarAlwaysVisible: {
+ set: function(val) {
+ if (!this.$hScrollBarAlwaysVisible || !this.$horizScroll)
+ this.$loop.schedule(this.CHANGE_SCROLL);
+ },
+ initialValue: false
+ },
+ vScrollBarAlwaysVisible: {
+ set: function(val) {
+ if (!this.$vScrollBarAlwaysVisible || !this.$vScroll)
+ this.$loop.schedule(this.CHANGE_SCROLL);
+ },
+ initialValue: false
+ },
+ fontSize: {
+ set: function(size) {
+ if (typeof size == "number")
+ size = size + "px";
+ this.container.style.fontSize = size;
+ this.updateFontSize();
+ },
+ initialValue: 12
+ },
+ fontFamily: {
+ set: function(name) {
+ this.container.style.fontFamily = name;
+ this.updateFontSize();
+ }
+ },
+ maxLines: {
+ set: function(val) {
+ this.updateFull();
+ }
+ },
+ minLines: {
+ set: function(val) {
+ this.updateFull();
+ }
+ },
+ scrollPastEnd: {
+ set: function(val) {
+ val = +val || 0;
+ if (this.$scrollPastEnd == val)
+ return;
+ this.$scrollPastEnd = val;
+ this.$loop.schedule(this.CHANGE_SCROLL);
+ },
+ initialValue: 0,
+ handlesSet: true
+ },
+ fixedWidthGutter: {
+ set: function(val) {
+ this.$gutterLayer.$fixedWidth = !!val;
+ this.$loop.schedule(this.CHANGE_GUTTER);
+ }
+ },
+ theme: {
+ set: function(val) { this.setTheme(val) },
+ get: function() { return this.$themeId || this.theme; },
+ initialValue: "./theme/textmate",
+ handlesSet: true
+ }
+});
exports.VirtualRenderer = VirtualRenderer;
});