mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
668 lines
24 KiB
JavaScript
668 lines
24 KiB
JavaScript
|
/* ***** 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("<span class='ace_invisible'>"
|
||
|
+ this.TAB_CHAR
|
||
|
+ lang.stringRepeat("\xa0", i - 1)
|
||
|
+ "</span>");
|
||
|
} 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[" "] = "<span class='" + className + "'>" + spaceContent + "</span>";
|
||
|
this.$tabStrings["\t"] = "<span class='" + className + "'>" + tabContent + "</span>";
|
||
|
}
|
||
|
};
|
||
|
|
||
|
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("<div class='ace_line_group' style='height:", config.lineHeight*this.session.getRowLength(row), "px'>")
|
||
|
|
||
|
this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
|
||
|
|
||
|
if (this.$useLineGroups())
|
||
|
html.push("</div>"); // end the line group
|
||
|
|
||
|
row++;
|
||
|
}
|
||
|
this.element = dom.setInnerHtml(this.element, html.join(""));
|
||
|
};
|
||
|
|
||
|
this.$textToken = {
|
||
|
"text": true,
|
||
|
"rparen": true,
|
||
|
"lparen": true
|
||
|
};
|
||
|
|
||
|
this.$renderToken = function(stringBuilder, screenColumn, token, value) {
|
||
|
var self = this;
|
||
|
var replaceReg = /\t|&|<|( +)|([\x00-\x1f\x80-\xa0\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\u3000\uFEFF])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]/g;
|
||
|
var replaceFunc = function(c, a, b, tabIdx, idx4) {
|
||
|
if (a) {
|
||
|
return self.showInvisibles ?
|
||
|
"<span class='ace_invisible'>" + lang.stringRepeat(self.SPACE_CHAR, c.length) + "</span>" :
|
||
|
lang.stringRepeat("\xa0", c.length);
|
||
|
} else if (c == "&") {
|
||
|
return "&";
|
||
|
} else if (c == "<") {
|
||
|
return "<";
|
||
|
} else if (c == "\t") {
|
||
|
var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
|
||
|
screenColumn += tabSize - 1;
|
||
|
return self.$tabStrings[tabSize];
|
||
|
} else if (c == "\u3000") {
|
||
|
// U+3000 is both invisible AND full-width, so must be handled uniquely
|
||
|
var classToUse = self.showInvisibles ? "ace_cjk ace_invisible" : "ace_cjk";
|
||
|
var space = self.showInvisibles ? self.SPACE_CHAR : "";
|
||
|
screenColumn += 1;
|
||
|
return "<span class='" + classToUse + "' style='width:" +
|
||
|
(self.config.characterWidth * 2) +
|
||
|
"px'>" + space + "</span>";
|
||
|
} else if (b) {
|
||
|
return "<span class='ace_invisible ace_invalid'>" + self.SPACE_CHAR + "</span>";
|
||
|
} else {
|
||
|
screenColumn += 1;
|
||
|
return "<span class='ace_cjk' style='width:" +
|
||
|
(self.config.characterWidth * 2) +
|
||
|
"px'>" + c + "</span>";
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var output = value.replace(replaceReg, replaceFunc);
|
||
|
|
||
|
if (!this.$textToken[token.type]) {
|
||
|
var classes = "ace_" + token.type.replace(/\./g, " ace_");
|
||
|
var style = "";
|
||
|
if (token.type == "fold")
|
||
|
style = " style='width:" + (token.value.length * this.config.characterWidth) + "px;' ";
|
||
|
stringBuilder.push("<span class='", classes, "'", style, ">", output, "</span>");
|
||
|
}
|
||
|
else {
|
||
|
stringBuilder.push(output);
|
||
|
}
|
||
|
return screenColumn + value.length;
|
||
|
};
|
||
|
|
||
|
this.renderIndentGuide = function(stringBuilder, value, max) {
|
||
|
var cols = value.search(this.$indentGuideRe);
|
||
|
if (cols <= 0 || cols >= max)
|
||
|
return value;
|
||
|
if (value[0] == " ") {
|
||
|
cols -= cols % this.tabSize;
|
||
|
stringBuilder.push(lang.stringRepeat(this.$tabStrings[" "], cols/this.tabSize));
|
||
|
return value.substr(cols);
|
||
|
} else if (value[0] == "\t") {
|
||
|
stringBuilder.push(lang.stringRepeat(this.$tabStrings["\t"], cols));
|
||
|
return value.substr(cols);
|
||
|
}
|
||
|
return value;
|
||
|
};
|
||
|
|
||
|
this.$renderWrappedLine = function(stringBuilder, tokens, splits, onlyContents) {
|
||
|
var chars = 0;
|
||
|
var split = 0;
|
||
|
var splitChars = splits[0];
|
||
|
var screenColumn = 0;
|
||
|
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var token = tokens[i];
|
||
|
var value = token.value;
|
||
|
if (i == 0 && this.displayIndentGuides) {
|
||
|
chars = value.length;
|
||
|
value = this.renderIndentGuide(stringBuilder, value, splitChars);
|
||
|
if (!value)
|
||
|
continue;
|
||
|
chars -= value.length;
|
||
|
}
|
||
|
|
||
|
if (chars + value.length < splitChars) {
|
||
|
screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
|
||
|
chars += value.length;
|
||
|
} else {
|
||
|
while (chars + value.length >= splitChars) {
|
||
|
screenColumn = this.$renderToken(
|
||
|
stringBuilder, screenColumn,
|
||
|
token, value.substring(0, splitChars - chars)
|
||
|
);
|
||
|
value = value.substring(splitChars - chars);
|
||
|
chars = splitChars;
|
||
|
|
||
|
if (!onlyContents) {
|
||
|
stringBuilder.push("</div>",
|
||
|
"<div class='ace_line' style='height:",
|
||
|
this.config.lineHeight, "px'>"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
split ++;
|
||
|
screenColumn = 0;
|
||
|
splitChars = splits[split] || Number.MAX_VALUE;
|
||
|
}
|
||
|
if (value.length != 0) {
|
||
|
chars += value.length;
|
||
|
screenColumn = this.$renderToken(
|
||
|
stringBuilder, screenColumn, token, value
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.$renderSimpleLine = function(stringBuilder, tokens) {
|
||
|
var screenColumn = 0;
|
||
|
var token = tokens[0];
|
||
|
var value = token.value;
|
||
|
if (this.displayIndentGuides)
|
||
|
value = this.renderIndentGuide(stringBuilder, value);
|
||
|
if (value)
|
||
|
screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
|
||
|
for (var i = 1; i < tokens.length; i++) {
|
||
|
token = tokens[i];
|
||
|
value = token.value;
|
||
|
screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// row is either first row of foldline or not in fold
|
||
|
this.$renderLine = function(stringBuilder, row, onlyContents, foldLine) {
|
||
|
if (!foldLine && foldLine != false)
|
||
|
foldLine = this.session.getFoldLine(row);
|
||
|
|
||
|
if (foldLine)
|
||
|
var tokens = this.$getFoldLineTokens(row, foldLine);
|
||
|
else
|
||
|
var tokens = this.session.getTokens(row);
|
||
|
|
||
|
|
||
|
if (!onlyContents) {
|
||
|
stringBuilder.push(
|
||
|
"<div class='ace_line' style='height:",
|
||
|
this.config.lineHeight * (
|
||
|
this.$useLineGroups() ? 1 :this.session.getRowLength(row)
|
||
|
), "px'>"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (tokens.length) {
|
||
|
var splits = this.session.getRowSplitData(row);
|
||
|
if (splits && splits.length)
|
||
|
this.$renderWrappedLine(stringBuilder, tokens, splits, onlyContents);
|
||
|
else
|
||
|
this.$renderSimpleLine(stringBuilder, tokens);
|
||
|
}
|
||
|
|
||
|
if (this.showInvisibles) {
|
||
|
if (foldLine)
|
||
|
row = foldLine.end.row
|
||
|
|
||
|
stringBuilder.push(
|
||
|
"<span class='ace_invisible'>",
|
||
|
row == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR,
|
||
|
"</span>"
|
||
|
);
|
||
|
}
|
||
|
if (!onlyContents)
|
||
|
stringBuilder.push("</div>");
|
||
|
};
|
||
|
|
||
|
this.$getFoldLineTokens = function(row, foldLine) {
|
||
|
var session = this.session;
|
||
|
var renderTokens = [];
|
||
|
|
||
|
function addTokens(tokens, from, to) {
|
||
|
var idx = 0, col = 0;
|
||
|
while ((col + tokens[idx].value.length) < from) {
|
||
|
col += tokens[idx].value.length;
|
||
|
idx++;
|
||
|
|
||
|
if (idx == tokens.length)
|
||
|
return;
|
||
|
}
|
||
|
if (col != from) {
|
||
|
var value = tokens[idx].value.substring(from - col);
|
||
|
// Check if the token value is longer then the from...to spacing.
|
||
|
if (value.length > (to - from))
|
||
|
value = value.substring(0, to - from);
|
||
|
|
||
|
renderTokens.push({
|
||
|
type: tokens[idx].type,
|
||
|
value: value
|
||
|
});
|
||
|
|
||
|
col = from + value.length;
|
||
|
idx += 1;
|
||
|
}
|
||
|
|
||
|
while (col < to && idx < tokens.length) {
|
||
|
var value = tokens[idx].value;
|
||
|
if (value.length + col > to) {
|
||
|
renderTokens.push({
|
||
|
type: tokens[idx].type,
|
||
|
value: value.substring(0, to - col)
|
||
|
});
|
||
|
} else
|
||
|
renderTokens.push(tokens[idx]);
|
||
|
col += value.length;
|
||
|
idx += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var tokens = session.getTokens(row);
|
||
|
foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
|
||
|
if (placeholder != null) {
|
||
|
renderTokens.push({
|
||
|
type: "fold",
|
||
|
value: placeholder
|
||
|
});
|
||
|
} else {
|
||
|
if (isNewRow)
|
||
|
tokens = session.getTokens(row);
|
||
|
|
||
|
if (tokens.length)
|
||
|
addTokens(tokens, lastColumn, column);
|
||
|
}
|
||
|
}, foldLine.end.row, this.session.getLine(foldLine.end.row).length);
|
||
|
|
||
|
return renderTokens;
|
||
|
};
|
||
|
|
||
|
this.$useLineGroups = function() {
|
||
|
// For the updateLines function to work correctly, it's important that the
|
||
|
// child nodes of this.element correspond on a 1-to-1 basis to rows in the
|
||
|
// document (as distinct from lines on the screen). For sessions that are
|
||
|
// wrapped, this means we need to add a layer to the node hierarchy (tagged
|
||
|
// with the class name ace_line_group).
|
||
|
return this.session.getUseWrapMode();
|
||
|
};
|
||
|
|
||
|
this.destroy = function() {
|
||
|
clearInterval(this.$pollSizeChangesTimer);
|
||
|
if (this.$measureNode)
|
||
|
this.$measureNode.parentNode.removeChild(this.$measureNode);
|
||
|
delete this.$measureNode;
|
||
|
};
|
||
|
|
||
|
}).call(Text.prototype);
|
||
|
|
||
|
exports.Text = Text;
|
||
|
|
||
|
});
|