/* ***** 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 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ define(function(require, exports, module) { "use strict"; var oop = require("../lib/oop"); var dom = require("../lib/dom"); var lang = require("../lib/lang"); var useragent = require("../lib/useragent"); var EventEmitter = require("../lib/event_emitter").EventEmitter; var Text = function(parentEl) { this.element = dom.createElement("div"); this.element.className = "ace_layer ace_text-layer"; parentEl.appendChild(this.element); this.$characterSize = {width: 0, height: 0}; this.checkForSizeChanges(); this.$pollSizeChanges(); }; (function() { oop.implement(this, EventEmitter); this.EOF_CHAR = "\xB6"; //"¶"; this.EOL_CHAR = "\xAC"; //"¬"; this.TAB_CHAR = "\u2192"; //"→" "\u21E5"; this.SPACE_CHAR = "\xB7"; //"·"; this.$padding = 0; this.setPadding = function(padding) { this.$padding = padding; this.element.style.padding = "0 " + padding + "px"; }; this.getLineHeight = function() { return this.$characterSize.height || 0; }; this.getCharacterWidth = function() { return this.$characterSize.width || 0; }; this.checkForSizeChanges = function() { var size = this.$measureSizes(); if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) { this.$measureNode.style.fontWeight = "bold"; var boldSize = this.$measureSizes(); this.$measureNode.style.fontWeight = ""; this.$characterSize = size; this.allowBoldFonts = boldSize && boldSize.width === size.width && boldSize.height === size.height; this._emit("changeCharacterSize", {data: size}); } }; this.$pollSizeChanges = function() { var self = this; this.$pollSizeChangesTimer = setInterval(function() { self.checkForSizeChanges(); }, 500); }; this.$fontStyles = { fontFamily : 1, fontSize : 1, fontWeight : 1, fontStyle : 1, lineHeight : 1 }; this.$measureSizes = useragent.isIE || useragent.isOldGecko ? function() { var n = 1000; if (!this.$measureNode) { var measureNode = this.$measureNode = dom.createElement("div"); var style = measureNode.style; style.width = style.height = "auto"; style.left = style.top = (-n * 40) + "px"; style.visibility = "hidden"; style.position = "fixed"; style.overflow = "visible"; style.whiteSpace = "nowrap"; // in FF 3.6 monospace fonts can have a fixed sub pixel width. // that's why we have to measure many characters // Note: characterWidth can be a float! measureNode.innerHTML = lang.stringRepeat("Xy", n); if (this.element.ownerDocument.body) { this.element.ownerDocument.body.appendChild(measureNode); } else { var container = this.element.parentNode; while (!dom.hasCssClass(container, "ace_editor")) container = container.parentNode; container.appendChild(measureNode); } } // Size and width can be null if the editor is not visible or // detached from the document if (!this.element.offsetWidth) return null; var style = this.$measureNode.style; var computedStyle = dom.computedStyle(this.element); for (var prop in this.$fontStyles) style[prop] = computedStyle[prop]; var size = { height: this.$measureNode.offsetHeight, width: this.$measureNode.offsetWidth / (n * 2) }; // Size and width can be null if the editor is not visible or // detached from the document if (size.width == 0 || size.height == 0) return null; return size; } : function() { if (!this.$measureNode) { var measureNode = this.$measureNode = dom.createElement("div"); var style = measureNode.style; style.width = style.height = "auto"; style.left = style.top = -100 + "px"; style.visibility = "hidden"; style.position = "fixed"; style.overflow = "visible"; style.whiteSpace = "nowrap"; // fixes fractional fixed-width fonts; see http://git.io/CavZNw measureNode.innerHTML = lang.stringRepeat("X", 100); var container = this.element.parentNode; while (container && !dom.hasCssClass(container, "ace_editor")) container = container.parentNode; if (!container) return this.$measureNode = null; container.appendChild(measureNode); } var rect = this.$measureNode.getBoundingClientRect(); var size = { height: rect.height, width: rect.width / 100 }; // Size and width can be null if the editor is not visible or // detached from the document if (size.width == 0 || size.height == 0) return null; return size; }; this.setSession = function(session) { this.session = session; this.$computeTabString(); }; this.showInvisibles = false; this.setShowInvisibles = function(showInvisibles) { if (this.showInvisibles == showInvisibles) return false; this.showInvisibles = showInvisibles; this.$computeTabString(); return true; }; this.displayIndentGuides = true; this.setDisplayIndentGuides = function(display) { if (this.displayIndentGuides == display) return false; this.displayIndentGuides = display; this.$computeTabString(); return true; }; this.$tabStrings = []; this.onChangeTabSize = this.$computeTabString = function() { var tabSize = this.session.getTabSize(); this.tabSize = tabSize; var tabStr = this.$tabStrings = [0]; for (var i = 1; i < tabSize + 1; i++) { if (this.showInvisibles) { tabStr.push("" + this.TAB_CHAR + lang.stringRepeat("\xa0", i - 1) + ""); } else { tabStr.push(lang.stringRepeat("\xa0", i)); } } if (this.displayIndentGuides) { this.$indentGuideRe = /\s\S| \t|\t |\s$/; var className = "ace_indent-guide"; if (this.showInvisibles) { className += " ace_invisible"; var spaceContent = lang.stringRepeat(this.SPACE_CHAR, this.tabSize); var tabContent = this.TAB_CHAR + lang.stringRepeat("\xa0", this.tabSize - 1); } else{ var spaceContent = lang.stringRepeat("\xa0", this.tabSize); var tabContent = spaceContent; } this.$tabStrings[" "] = "" + spaceContent + ""; this.$tabStrings["\t"] = "" + tabContent + ""; } }; this.updateLines = function(config, firstRow, lastRow) { // Due to wrap line changes there can be new lines if e.g. // the line to updated wrapped in the meantime. if (this.config.lastRow != config.lastRow || this.config.firstRow != config.firstRow) { this.scrollLines(config); } this.config = config; var first = Math.max(firstRow, config.firstRow); var last = Math.min(lastRow, config.lastRow); var lineElements = this.element.childNodes; var lineElementsIdx = 0; for (var row = config.firstRow; row < first; row++) { var foldLine = this.session.getFoldLine(row); if (foldLine) { if (foldLine.containsRow(first)) { first = foldLine.start.row; break; } else { row = foldLine.end.row; } } lineElementsIdx ++; } var row = first; var foldLine = this.session.getNextFoldLine(row); var foldStart = foldLine ? foldLine.start.row : Infinity; while (true) { if (row > foldStart) { row = foldLine.end.row+1; foldLine = this.session.getNextFoldLine(row, foldLine); foldStart = foldLine ? foldLine.start.row :Infinity; } if (row > last) break; var lineElement = lineElements[lineElementsIdx++]; if (lineElement) { var html = []; this.$renderLine( html, row, !this.$useLineGroups(), row == foldStart ? foldLine : false ); lineElement.style.height = config.lineHeight * this.session.getRowLength(row) + "px"; dom.setInnerHtml(lineElement, html.join("")); } row++; } }; this.scrollLines = function(config) { var oldConfig = this.config; this.config = config; if (!oldConfig || oldConfig.lastRow < config.firstRow) return this.update(config); if (config.lastRow < oldConfig.firstRow) return this.update(config); var el = this.element; if (oldConfig.firstRow < config.firstRow) for (var row=this.session.getFoldedRowCount(oldConfig.firstRow, config.firstRow - 1); row>0; row--) el.removeChild(el.firstChild); if (oldConfig.lastRow > config.lastRow) for (var row=this.session.getFoldedRowCount(config.lastRow + 1, oldConfig.lastRow); row>0; row--) el.removeChild(el.lastChild); if (config.firstRow < oldConfig.firstRow) { var fragment = this.$renderLinesFragment(config, config.firstRow, oldConfig.firstRow - 1); if (el.firstChild) el.insertBefore(fragment, el.firstChild); else el.appendChild(fragment); } if (config.lastRow > oldConfig.lastRow) { var fragment = this.$renderLinesFragment(config, oldConfig.lastRow + 1, config.lastRow); el.appendChild(fragment); } }; this.$renderLinesFragment = function(config, firstRow, lastRow) { var fragment = this.element.ownerDocument.createDocumentFragment(); var row = firstRow; var foldLine = this.session.getNextFoldLine(row); var foldStart = foldLine ? foldLine.start.row : Infinity; while (true) { if (row > foldStart) { row = foldLine.end.row+1; foldLine = this.session.getNextFoldLine(row, foldLine); foldStart = foldLine ? foldLine.start.row : Infinity; } if (row > lastRow) break; var container = dom.createElement("div"); var html = []; // Get the tokens per line as there might be some lines in between // beeing folded. this.$renderLine(html, row, false, row == foldStart ? foldLine : false); // don't use setInnerHtml since we are working with an empty DIV container.innerHTML = html.join(""); if (this.$useLineGroups()) { container.className = 'ace_line_group'; fragment.appendChild(container); container.style.height = config.lineHeight * this.session.getRowLength(row) + "px"; } else { var lines = container.childNodes while(lines.length) fragment.appendChild(lines[0]); } row++; } return fragment; }; this.update = function(config) { this.config = config; var html = []; var firstRow = config.firstRow, lastRow = config.lastRow; var row = firstRow; var foldLine = this.session.getNextFoldLine(row); var foldStart = foldLine ? foldLine.start.row : Infinity; while (true) { if (row > foldStart) { row = foldLine.end.row+1; foldLine = this.session.getNextFoldLine(row, foldLine); foldStart = foldLine ? foldLine.start.row :Infinity; } if (row > lastRow) break; if (this.$useLineGroups()) html.push("