| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- /* ***** 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.$updateEolChar = this.$updateEolChar.bind(this);
- };
- (function() {
- oop.implement(this, EventEmitter);
- this.EOF_CHAR = "\xB6";
- this.EOL_CHAR_LF = "\xAC";
- this.EOL_CHAR_CRLF = "\xa4";
- this.EOL_CHAR = this.EOL_CHAR_LF;
- this.TAB_CHAR = "\u2014"; //"\u21E5";
- this.SPACE_CHAR = "\xB7";
- this.$padding = 0;
- this.$updateEolChar = function() {
- var EOL_CHAR = this.session.doc.getNewLineCharacter() == "\n"
- ? this.EOL_CHAR_LF
- : this.EOL_CHAR_CRLF;
- if (this.EOL_CHAR != EOL_CHAR) {
- this.EOL_CHAR = EOL_CHAR;
- return true;
- }
- }
- this.setPadding = function(padding) {
- this.$padding = padding;
- this.element.style.padding = "0 " + padding + "px";
- };
- this.getLineHeight = function() {
- return this.$fontMetrics.$characterSize.height || 0;
- };
- this.getCharacterWidth = function() {
- return this.$fontMetrics.$characterSize.width || 0;
- };
-
- this.$setFontMetrics = function(measure) {
- this.$fontMetrics = measure;
- this.$fontMetrics.on("changeCharacterSize", function(e) {
- this._signal("changeCharacterSize", e);
- }.bind(this));
- this.$pollSizeChanges();
- }
- this.checkForSizeChanges = function() {
- this.$fontMetrics.checkForSizeChanges();
- };
- this.$pollSizeChanges = function() {
- return this.$pollSizeChangesTimer = this.$fontMetrics.$pollSizeChanges();
- };
- this.setSession = function(session) {
- this.session = session;
- if (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 ace_invisible_tab'>"
- + lang.stringRepeat(this.TAB_CHAR, i)
- + "</span>");
- } else {
- tabStr.push(lang.stringRepeat(" ", i));
- }
- }
- if (this.displayIndentGuides) {
- this.$indentGuideRe = /\s\S| \t|\t |\s$/;
- var className = "ace_indent-guide";
- var spaceClass = "";
- var tabClass = "";
- if (this.showInvisibles) {
- className += " ace_invisible";
- spaceClass = " ace_invisible_space";
- tabClass = " ace_invisible_tab";
- var spaceContent = lang.stringRepeat(this.SPACE_CHAR, this.tabSize);
- var tabContent = lang.stringRepeat(this.TAB_CHAR, this.tabSize);
- } else{
- var spaceContent = lang.stringRepeat(" ", this.tabSize);
- var tabContent = spaceContent;
- }
- this.$tabStrings[" "] = "<span class='" + className + spaceClass + "'>" + spaceContent + "</span>";
- this.$tabStrings["\t"] = "<span class='" + className + tabClass + "'>" + 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";
- lineElement.innerHTML = 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 {
- while(container.firstChild)
- fragment.appendChild(container.firstChild);
- }
- 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.innerHTML = 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\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\u3000\uFEFF\uFFF9-\uFFFC])|[\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 ace_invisible_space'>" + lang.stringRepeat(self.SPACE_CHAR, c.length) + "</span>"
- : c;
- } else if (c == "&") {
- return "&";
- } else if (c == "<") {
- return "<";
- } else if (c == ">") {
- // normally escaping this is not needed, but xml documents throw error when setting innerHTML to ]]>
- return ">";
- } else if (c == "\t") {
- if (typeof self.session !== 'undefined') {
- var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
- screenColumn += tabSize - 1;
- return self.$tabStrings[tabSize];
- }
- return "<span class='ace_invisible ace_invisible_tab'>	</span>";
- } 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_invisible_space" : "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_invisible_space 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'>"
- );
- }
- stringBuilder.push(lang.stringRepeat("\xa0", splits.indent));
- 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 ace_invisible_eol'>",
- 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;
- });
|