From 588cf4e7f4effa8a0a16a234e5ed453356370834 Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Tue, 8 Nov 2016 15:37:02 +0200 Subject: [PATCH 01/10] initial submit --- lib/ace/bidihandler.js | 334 ++++++++++++++++ lib/ace/edit_session.js | 20 +- lib/ace/keyboard/emacs.js | 8 +- lib/ace/layer/cursor.js | 5 +- lib/ace/layer/marker.js | 100 +++-- lib/ace/layer/text.js | 23 ++ lib/ace/lib/bidiutil.js | 555 ++++++++++++++++++++++++++ lib/ace/mouse/multi_select_handler.js | 2 +- lib/ace/multi_select.js | 8 +- lib/ace/virtual_renderer.js | 26 +- 10 files changed, 1033 insertions(+), 48 deletions(-) create mode 100644 lib/ace/bidihandler.js create mode 100644 lib/ace/lib/bidiutil.js diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js new file mode 100644 index 00000000000..f0951114df8 --- /dev/null +++ b/lib/ace/bidihandler.js @@ -0,0 +1,334 @@ +/* ***** 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 bidiUtil = require("./lib/bidiutil"); +var lang = require("./lib/lang"); + +/** + * This object is used to ensure Bi-Directional support (for languages with text flowing from right to left, like Arabic or Hebrew) + * including correct caret positioning, text selection mouse and keyboard arrows functioning + * @class BidiHandler + **/ + +/** + * Creates a new `BidiHandler` object + * @param {EditSession} session The session to use + * + * @constructor + **/ +var BidiHandler = function(session) { + this.session = session; + this.bidiMap = {}; + /* current screen row */ + this.currentRow = null; + this.bidiUtil = bidiUtil; + /* Arabic/Hebrew character width differs from regular character width */ + this.charWidths = []; + this.EOL; + this.showInvisibles; + this.isRtlDir = false; + this.line = ""; + this.wrapIndent = 0; + /* font face has been set to Courier to ensure monospaced Bidi characters in Chrome/Opera */ + this.isCourierFont = false; +}; + +(function() { + /** + * Returns 'true' if row contains Bidi characters, in such case + * creates Bidi map to be used in operations related to selection + * (keyboard arrays, mouse click, select) + * @param {Number} the screen row to be checked + * @param {Number} the document row to be checked [optional] + * @param {Number} the wrapped screen line index [ optional] + **/ + this.isBidiRow = function(screenRow, docRow, splitIndex) { + if (screenRow !== this.currentRow) { + this.currentRow = screenRow; + this.updateRowLine(docRow, splitIndex); + this.updateBidiMap(); + } + return this.bidiMap.bidiLevels; + }; + + this.hasBidiCharacters = function(str) { + return /[\u0591-\u06ff\ufb1d-\ufefc]/.test(str); + }; + + this.getDocumentRow = function() { + var docRow = 0; + var rowCache = this.session.$screenRowCache; + if (rowCache.length) { + var index = this.session.$getRowCacheIndex(rowCache, this.currentRow); + if (index >= 0) + docRow = this.session.$docRowCache[index]; + } + + return docRow; + }; + + this.getSplitIndex = function() { + var splitIndex = 0; + var rowCache = this.session.$screenRowCache; + if (rowCache.length) { + var currentIndex, prevIndex = this.session.$getRowCacheIndex(rowCache, this.currentRow); + while (this.currentRow - splitIndex > 0) { + currentIndex = this.session.$getRowCacheIndex(rowCache, this.currentRow - splitIndex - 1); + if (currentIndex !== prevIndex) + break; + + prevIndex = currentIndex; + splitIndex++; + } + } + + return splitIndex; + }; + + this.updateRowLine = function(docRow, splitIndex) { + if (docRow === undefined) + docRow = this.getDocumentRow(); + + this.wrapIndent = 0; + this.line = this.session.getLine(docRow); + if (this.session.$useWrapMode) { + var splits = this.session.$wrapData[docRow]; + if (splits) { + if (splitIndex === undefined) + splitIndex = this.getSplitIndex(); + + if(splitIndex > 0 && splits.length) { + this.wrapIndent = splits.indent; + this.line = (splitIndex < splits.length) ? + this.line.substring(splits[splitIndex - 1], splits[splits.length - 1]) : + this.line.substring(splits[splits.length - 1]); + } else { + this.line = this.line.substring(0, splits[splitIndex]); + } + } + } + + /* replace tab and wide characters by commensurate spaces */ + var session = this.session, shift = 0, size; + this.line = this.line.replace(/\t|[\u1100-\uFFE6]/g, function(ch, i){ + if (ch === '\t' || session.isFullWidth(ch.charCodeAt(0))) { + size = (ch === '\t') ? session.getScreenTabSize(i + shift) : 2; + shift += size - 1; + return lang.stringRepeat(bidiUtil.DOT, size); + } + }); + }; + + this.updateBidiMap = function() { + var textCharTypes = []; + var line = this.line + (this.showInvisibles ? this.EOL : bidiUtil.DOT); + if (bidiUtil.hasBidiCharacters(line, textCharTypes)) { + this.bidiMap = bidiUtil.doBidiReorder(line, textCharTypes, this.isRtlDir); + } else { + this.bidiMap = {}; + } + }; + + /** + * Resets stored info related to current screen row + **/ + this.markAsDirty = function() { + this.currentRow = null; + }; + + /** + * Updates array of character widths + * @param {boolena} is chrome based engine + * @param {Object} font metrics + * + **/ + this.updateCharacterWidths = function(isChrome, $fontMetrics) { + var _regularCharWidth = $fontMetrics.$measureCharWidth('a'), _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); + /* on some Windows7 OS not 'Monaco' but 'Courier' family is true monospaced font for Bidi characters in Chrome based browsers */ + if (isChrome && (_bidiCharWidth !== $fontMetrics.$measureCharWidth('\u05d5'))) { + this.isCourierFont = true; + $fontMetrics.$main.style.fontFamily = "Courier"; + _regularCharWidth = $fontMetrics.$measureCharWidth('a'); + _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); + $fontMetrics.$main.style.fontFamily = "inherit"; + } + + this.charWidths[bidiUtil.L] = this.charWidths[bidiUtil.EN] = this.charWidths[bidiUtil.ON_R] = _regularCharWidth; + this.charWidths[bidiUtil.R] = this.charWidths[bidiUtil.AN] = _bidiCharWidth; + this.charWidths[bidiUtil.R_H] = isChrome ? _bidiCharWidth : _bidiCharWidth * 0.45; + this.charWidths[bidiUtil.B] = 0; + + if (this.currentRow !== null) { + this.updateBidiMap(); + } + }; + + this.isCourierFontFace = function() { + return this.isCourierFont; + }; + + this.getShowInvisibles = function() { + return this.showInvisibles; + }; + + this.setShowInvisibles = function(showInvisibles) { + this.showInvisibles = showInvisibles; + if (this.currentRow !== null) { + this.updateBidiMap(); + } + }; + + this.setEolChar = function(eolChar) { + this.EOL = eolChar; + }; + + this.setTextDir = function(isRtlDir) { + this.isRtlDir = isRtlDir; + }; + + /** + * Returns offset of character at position defined by column. + * @param {Number} the screen column position + * + * @return {int} horizontal pixel offset of given screen column + **/ + this.getPosLeft = function(col) { + col -= this.wrapIndent; + var visualIdx = bidiUtil.getVisualFromLogicalIdx(col > 0 ? col - 1 : 0, this.bidiMap), + levels = this.bidiMap.bidiLevels, left = 0; + + if (col === 0 && levels[visualIdx] % 2 !== 0) + visualIdx++; + + for (var i = 0; i < visualIdx; i++) { + left += this.charWidths[levels[i]]; + } + + if (col !== 0 && levels[visualIdx] % 2 === 0) + left += this.charWidths[levels[visualIdx]]; + + if (this.wrapIndent) + left += this.wrapIndent * this.charWidths[bidiUtil.L]; + + return left; + }; + + /** + * Returns 'selections' - array of objects defining set of selection rectangles + * @param {Number} the start column position + * @param {Number} the end column position + * + * @return {Array of Objects} Each object contains 'left' and 'width' values defining selection rectangle. + **/ + this.getSelections = function(startCol, endCol) { + var map = this.bidiMap, levels = map.bidiLevels, level, offset = this.wrapIndent * this.charWidths[bidiUtil.L], selections = [], + selColMin = Math.min(startCol, endCol) - this.wrapIndent, selColMax = Math.max(startCol, endCol) - this.wrapIndent, + isSelected = false, isSelectedPrev = false, selectionStart = 0; + + for (var logIdx, visIdx = 0; visIdx < levels.length; visIdx++) { + logIdx = map.logicalFromVisual[visIdx]; + level = levels[visIdx]; + isSelected = (logIdx >= selColMin) && (logIdx < selColMax); + if (isSelected && !isSelectedPrev) { + selectionStart = offset; + } else if (!isSelected && isSelectedPrev) { + selections.push({left: selectionStart, width: offset - selectionStart}); + } + offset += this.charWidths[level]; + isSelectedPrev = isSelected; + } + + if (isSelected && (visIdx === levels.length)) { + selections.push({left: selectionStart, width: offset - selectionStart}); + } + + return selections; + }; + + /** + * Converts character coordinates on the screen to respective document column number + * @param {int} character horizontal offset + * + * @return {Number} screen column number corresponding to given pixel offset + **/ + this.offsetToCol = function(posX) { + var logicalIdx = 0, posX = Math.max(posX, 0), + offset = 0, visualIdx = 0, levels = this.bidiMap.bidiLevels, + charWidth = this.charWidths[levels[visualIdx]]; + + if (this.wrapIndent) { + posX -= this.wrapIndent * this.charWidths[bidiUtil.L]; + } + + while(posX > offset + charWidth/2) { + offset += charWidth; + if(visualIdx === levels.length - 1) { + /* quit when we on the right of the last character, flag this by charWidth = 0 */ + charWidth = 0; + break; + } + charWidth = this.charWidths[levels[++visualIdx]]; + } + + if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && (levels[visualIdx] % 2 === 0)){ + /* Bidi character on the left and None Bidi character on the right */ + if(posX < offset) + visualIdx--; + logicalIdx = this.bidiMap.logicalFromVisual[visualIdx]; + + } else if (visualIdx > 0 && (levels[visualIdx - 1] % 2 === 0) && (levels[visualIdx] % 2 !== 0)){ + /* None Bidi character on the left and Bidi character on the right */ + logicalIdx = 1 + ((posX > offset) ? this.bidiMap.logicalFromVisual[visualIdx] + : this.bidiMap.logicalFromVisual[visualIdx - 1]); + + } else if ((this.isRtlDir && visualIdx === levels.length - 1 && charWidth === 0 && (levels[visualIdx - 1] % 2 === 0)) + || (!this.isRtlDir && visualIdx === 0 && (levels[visualIdx] % 2 !== 0))){ + /* To the right of last character, which is None Bidi, in RTL direction or */ + /* to the left of first Bidi character, in LTR direction */ + logicalIdx = 1 + this.bidiMap.logicalFromVisual[visualIdx]; + } else { + /* Tweak visual position when Bidi character on the left in order to map it to corresponding logical position */ + if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && charWidth !== 0) + visualIdx--; + + /* Regular case */ + logicalIdx = this.bidiMap.logicalFromVisual[visualIdx]; + } + + return (logicalIdx + this.wrapIndent); + }; + +}).call(BidiHandler.prototype); + +exports.BidiHandler = BidiHandler; +}); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 0f75e2f2555..e9770a468f1 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -33,6 +33,7 @@ define(function(require, exports, module) { var oop = require("./lib/oop"); var lang = require("./lib/lang"); +var BidiHandler = require("./bidihandler").BidiHandler; var config = require("./config"); var EventEmitter = require("./lib/event_emitter").EventEmitter; var Selection = require("./selection").Selection; @@ -158,6 +159,7 @@ var EditSession = function(text, mode) { if (typeof text != "object" || !text.getLine) text = new Document(text); + this.$bidiHandler = new BidiHandler(this); this.setDocument(text); this.selection = new Selection(this); @@ -1116,6 +1118,7 @@ var EditSession = function(text, mode) { * **/ this.insert = function(position, text) { + this.$bidiHandler.markAsDirty(); return this.doc.insert(position, text); }; @@ -1128,6 +1131,7 @@ var EditSession = function(text, mode) { * **/ this.remove = function(range) { + this.$bidiHandler.markAsDirty(); return this.doc.remove(range); }; @@ -1560,6 +1564,8 @@ var EditSession = function(text, mode) { if (this.$wrapLimitRange.min !== min || this.$wrapLimitRange.max !== max) { this.$wrapLimitRange = { min: min, max: max }; this.$modified = true; + this.$bidiHandler.markAsDirty(); + // This will force a recalculation of the wrap limit if (this.$useWrapMode) this._signal("changeWrapMode"); @@ -2139,11 +2145,13 @@ var EditSession = function(text, mode) { * Converts characters coordinates on the screen to characters coordinates within the document. [This takes into account code folding, word wrap, tab size, and any other visual modifications.]{: #conversionConsiderations} * @param {Number} screenRow The screen row to check * @param {Number} screenColumn The screen column to check + * @param {int} screen character x-offset [optional] + * * @returns {Object} The object returned has two properties: `row` and `column`. * * @related EditSession.documentToScreenPosition **/ - this.screenToDocumentPosition = function(screenRow, screenColumn) { + this.screenToDocumentPosition = function(screenRow, screenColumn, offsetX) { if (screenRow < 0) return {row: 0, column: 0}; @@ -2202,11 +2210,10 @@ var EditSession = function(text, mode) { line = this.getLine(docRow); foldLine = null; } - var wrapIndent = 0; + var wrapIndent = 0, splitIndex = Math.floor(screenRow - row); if (this.$useWrapMode) { var splits = this.$wrapData[docRow]; if (splits) { - var splitIndex = Math.floor(screenRow - row); column = splits[splitIndex]; if(splitIndex > 0 && splits.length) { wrapIndent = splits.indent; @@ -2216,6 +2223,9 @@ var EditSession = function(text, mode) { } } + if (offsetX !== undefined && this.$bidiHandler.isBidiRow(row + splitIndex, docRow, splitIndex)) + screenColumn = this.$bidiHandler.offsetToCol(offsetX); + docColumn += this.$getStringScreenWidth(line, screenColumn - wrapIndent)[1]; // We remove one character at the end so that the docColumn @@ -2430,6 +2440,10 @@ var EditSession = function(text, mode) { this.$stopWorker(); }; + this.isFullWidth = function(c) { + return isFullWidth(c); + }; + // For every keystroke this gets called once per char in the whole doc!! // Wouldn't hurt to make it a bit faster for c >= 0x1100 function isFullWidth(c) { diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index f3f4ed6322a..7322fa77fdf 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -38,15 +38,15 @@ var iSearchCommandModule = require("../commands/incremental_search_commands"); var screenToTextBlockCoordinates = function(x, y) { var canvasPos = this.scroller.getBoundingClientRect(); + var offsetX = x + this.scrollLeft - canvasPos.left - this.$padding; + + var col = Math.floor(offsetX / this.characterWidth); - var col = Math.floor( - (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth - ); var row = Math.floor( (y + this.scrollTop - canvasPos.top) / this.lineHeight ); - return this.session.screenToDocumentPosition(row, col); + return this.session.screenToDocumentPosition(row, col, offsetX); }; var HashHandler = require("./hash_handler").HashHandler; diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 682632e1c4c..b3f1e9154c7 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -170,7 +170,10 @@ var Cursor = function(parentEl) { if (!position) position = this.session.selection.getCursor(); var pos = this.session.documentToScreenPosition(position); - var cursorLeft = this.$padding + pos.column * this.config.characterWidth; + var cursorLeft = this.$padding + (this.session.$bidiHandler.isBidiRow(pos.row, position.row) ? + this.session.$bidiHandler.getPosLeft(pos.column) : + pos.column * this.config.characterWidth); + var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) * this.config.lineHeight; diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 9818d225478..2641c7763ba 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -78,7 +78,10 @@ var Marker = function(parentEl) { range = range.toScreenRange(this.session); if (marker.renderer) { var top = this.$getTop(range.start.row, config); - var left = this.$padding + range.start.column * config.characterWidth; + var left = this.$padding + (this.session.$bidiHandler.isBidiRow(range.start.row) ? + this.session.$bidiHandler.getPosLeft(range.start.column) : + range.start.column * config.characterWidth); + marker.renderer(html, range, left, top, config); } else if (marker.type == "fullLine") { this.drawFullLineMarker(html, range, marker.clazz, config); @@ -90,7 +93,11 @@ var Marker = function(parentEl) { else this.drawMultiLineMarker(html, range, marker.clazz, config); } else { - this.drawSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); + if (this.session.$bidiHandler.isBidiRow(range.start.row)) { + this.drawBidiSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); + } else { + this.drawSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); + } } } this.element.innerHTML = html.join(""); @@ -112,6 +119,7 @@ var Marker = function(parentEl) { var prev = 0; var curr = 0; var next = session.getScreenLastRowColumn(row); + var clazzModified = null; var lineRange = new Range(row, range.start.column, row, curr); for (; row <= end; row++) { lineRange.start.row = lineRange.end.row = row; @@ -120,10 +128,16 @@ var Marker = function(parentEl) { prev = curr; curr = next; next = row + 1 < end ? session.getScreenLastRowColumn(row + 1) : row == end ? 0 : range.end.column; - this.drawSingleLineMarker(stringBuilder, lineRange, - clazz + (row == start ? " ace_start" : "") + " ace_br" - + getBorderClass(row == start || row == start + 1 && range.start.column, prev < curr, curr > next, row == end), - layerConfig, row == end ? 0 : 1, extraStyle); + clazzModified = clazz + (row == start ? " ace_start" : "") + " ace_br" + + getBorderClass(row == start || row == start + 1 && range.start.column, prev < curr, curr > next, row == end); + + if (this.session.$bidiHandler.isBidiRow(row)) { + this.drawBidiSingleLineMarker(stringBuilder, lineRange, clazzModified, + layerConfig, row == end ? 0 : 1, extraStyle); + } else { + this.drawSingleLineMarker(stringBuilder, lineRange, clazzModified, + layerConfig, row == end ? 0 : 1, extraStyle); + } } }; @@ -131,31 +145,43 @@ var Marker = function(parentEl) { this.drawMultiLineMarker = function(stringBuilder, range, clazz, config, extraStyle) { // from selection start to the end of the line var padding = this.$padding; - var height = config.lineHeight; - var top = this.$getTop(range.start.row, config); - var left = padding + range.start.column * config.characterWidth; + var height, top, left; extraStyle = extraStyle || ""; - - stringBuilder.push( - "
" - ); - + if (this.session.$bidiHandler.isBidiRow(range.start.row)) { + var range1 = range.clone(); + range1.end.row = range1.start.row; + range1.end.column = this.session.getLine(range1.start.row).length; + this.drawBidiSingleLineMarker(stringBuilder, range1, clazz + " ace_br1 ace_start", config, null, extraStyle); + } else { + height = config.lineHeight; + top = this.$getTop(range.start.row, config); + left = padding + range.start.column * config.characterWidth; + stringBuilder.push( + "
" + ); + } // from start of the last line to the selection end - top = this.$getTop(range.end.row, config); - var width = range.end.column * config.characterWidth; - - stringBuilder.push( - "
" - ); - + if (this.session.$bidiHandler.isBidiRow(range.end.row)) { + var range1 = range.clone(); + range1.start.row = range1.end.row; + range1.start.column = 0; + this.drawBidiSingleLineMarker(stringBuilder, range1, clazz + " ace_br12", config, null, extraStyle); + } else { + var width = range.end.column * config.characterWidth; + height = config.lineHeight; + top = this.$getTop(range.end.row, config); + stringBuilder.push( + "
" + ); + } // all the complete lines height = (range.end.row - range.start.row - 1) * config.lineHeight; if (height <= 0) @@ -190,6 +216,22 @@ var Marker = function(parentEl) { ); }; + // Draws Bidi marker which covers part or whole width of a single screen line + this.drawBidiSingleLineMarker = function(stringBuilder, range, clazz, config, extraLength, extraStyle) { + var height = config.lineHeight, top = this.$getTop(range.start.row, config), padding = this.$padding; + var selections = this.session.$bidiHandler.getSelections(range.start.column, range.end.column); + + selections.forEach(function(selection) { + stringBuilder.push( + "
" + ); + }); + }; + this.drawFullLineMarker = function(stringBuilder, range, clazz, config, extraStyle) { var top = this.$getTop(range.start.row, config); var height = config.lineHeight; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 5a83d4b1d4d..e07e3c08ff6 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -204,6 +204,10 @@ var Text = function(parentEl) { ); lineElement.style.height = config.lineHeight * this.session.getRowLength(row) + "px"; lineElement.innerHTML = html.join(""); + if (this.session.$bidiHandler.isCourierFontFace()) { + lineElement.style.fontFamily = this.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? + "Courier" : ""; + } } row++; } @@ -266,6 +270,10 @@ var Text = function(parentEl) { // don't use setInnerHtml since we are working with an empty DIV container.innerHTML = html.join(""); + + if (this.session.$bidiHandler.isCourierFontFace()) + this.setFontFamily(container, this, false); + if (this.$useLineGroups()) { container.className = 'ace_line_group'; fragment.appendChild(container); @@ -311,6 +319,21 @@ var Text = function(parentEl) { row++; } this.element.innerHTML = html.join(""); + if (this.session.$bidiHandler.isCourierFontFace()) + this.setFontFamily(this.element, this, this.$useLineGroups()); + }; + + this.setFontFamily = function(element, self, isLineGroup) { + element.childNodes.forEach(function(lineElement) { + if (lineElement) { + if (isLineGroup) { + self.setFontFamily(lineElement, self, false); + } else { + lineElement.style.fontFamily = self.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? + "Courier" : ""; + } + } + }); }; this.$textToken = { diff --git a/lib/ace/lib/bidiutil.js b/lib/ace/lib/bidiutil.js new file mode 100644 index 00000000000..0a9a0339a2b --- /dev/null +++ b/lib/ace/lib/bidiutil.js @@ -0,0 +1,555 @@ +/* ***** 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 ArabicAlefBetIntervalsBegine = ['\u0621', '\u0641']; +var ArabicAlefBetIntervalsEnd = ['\u063A', '\u064a']; +var dir = 0, hiLevel = 0; +var lastArabic = false, hasUBAT_AL = false, hasUBAT_B = false, hasUBAT_S = false, hasBlockSep = false, hasSegSep = false; + +var impTab_LTR = [ + /* L, R, EN, AN, N, IL, Cond */ +/* 0 LTR text */ [ 0, 3, 0, 1, 0, 0, 0 ], +/* 1 LTR+AN */ [ 0, 3, 0, 1, 2, 2, 0 ], +/* 2 LTR+AN+N */ [ 0, 3, 0, 0x11, 2, 0, 1 ], +/* 3 RTL text */ [ 0, 3, 5, 5, 4, 1, 0 ], +/* 4 RTL cont */ [ 0, 3, 0x15, 0x15, 4, 0, 1 ], +/* 5 RTL+EN/AN */ [ 0, 3, 5, 5, 4, 2, 0 ] +]; + +var impTab_RTL = [ + /* L, R, EN, AN, N, IL, Cond */ +/* 0 RTL text */ [ 2, 0, 1, 1, 0, 1, 0 ], +/* 1 RTL+EN/AN */ [ 2, 0, 1, 1, 0, 2, 0 ], +/* 2 LTR text */ [ 2, 0, 2, 1, 3, 2, 0 ], +/* 3 LTR+cont */ [ 2, 0, 2, 0x21, 3, 1, 1 ] +]; + +var LTR = 0, RTL = 1; + +var L = 0; /* left to right */ +var R = 1; /* right to left */ +var EN = 2; /* European digit */ +var AN = 3; /* Arabic-Indic digit */ +var ON = 4; /* neutral */ +var B = 5; /* block separator */ +var S = 6; /* segment separator */ +var AL = 7; /* Arabic Letter */ +var WS = 8; /* white space */ +var CS = 9; /* common digit separator */ +var ES = 10; /* European digit separator */ +var ET = 11; /* European digit terminator */ +var NSM = 12; /* Non Spacing Mark */ +var LRE = 13; /* LRE */ +var RLE = 14; /* RLE */ +var PDF = 15; /* PDF */ +var LRO = 16; /* LRO */ +var RLO = 17; /* RLO */ +var BN = 18; /* Boundary Neutral */ + +var TBBASE = 100, TB00 = 100, TB05 = 101, TB06 = 102, TB07 = 103, TB20 = 104, TBFB = 105, TBFE = 106, TBFF = 107; + +var MasterTable = [ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ TB00, L , L , L , L , TB05, TB06, TB07, R , L , L , L , L , L , L , L , + /*1-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*2-*/ TB20, ON , ON , ON , L , ON , L , ON , L , ON , ON , ON , L , L , ON , ON , + /*3-*/ L , L , L , L , L , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*4-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , L , L , ON , + /*5-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*6-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*7-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*8-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*9-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , L , + /*A-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , ON , ON , ON , + /*B-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*C-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*D-*/ ON , ON , ON , ON , ON , ON , ON , L , L , ON , ON , L , L , ON , ON , L , + /*E-*/ L , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*F-*/ ON , ON , ON , ON , ON , ON , ON , ON , L , L , L , TBFB, AL , AL , TBFE, TBFF +]; + +var UnicodeTable = [ + [ /* Table 00: Unicode 00xx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ BN , BN , BN , BN , BN , BN , BN , BN , BN , S , B , S , WS , B , BN , BN , + /*1-*/ BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , B , B , B , S , + /*2-*/ WS , ON , ON , ET , ET , ET , ON , ON , ON , ON , ON , ES , CS , ES , CS , CS , + /*3-*/ EN , EN , EN , EN , EN , EN , EN , EN , EN , EN , CS , ON , ON , ON , ON , ON , + /*4-*/ ON , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*5-*/ L , L , L , L , L , L , L , L , L , L , L , ON , ON , ON , ON , ON , + /*6-*/ ON , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*7-*/ L , L , L , L , L , L , L , L , L , L , L , ON , ON , ON , ON , BN , + /*8-*/ BN , BN , BN , BN , BN , B , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , + /*9-*/ BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , BN , + /*A-*/ CS , ON , ET , ET , ET , ET , ON , ON , ON , ON , L , ON , ON , BN , ON , ON , + /*B-*/ ET , ET , EN , EN , ON , L , ON , ON , ON , EN , L , ON , ON , ON , ON , ON , + /*C-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*D-*/ L , L , L , L , L , L , L , ON , L , L , L , L , L , L , L , L , + /*E-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*F-*/ L , L , L , L , L , L , L , ON , L , L , L , L , L , L , L , L + ], + [ /* Table 01: Unicode 05xx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*1-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*2-*/ L , L , L , L , L , L , L , L , ON , ON , ON , ON , ON , ON , ON , ON , + /*3-*/ ON , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*4-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*5-*/ L , L , L , L , L , L , L , ON , ON , L , L , L , L , L , L , L , + /*6-*/ ON , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*7-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*8-*/ L , L , L , L , L , L , L , L , ON , L , ON , ON , ON , ON , ON , ON , + /*9-*/ ON , NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*A-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*B-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, R , NSM, + /*C-*/ R , NSM, NSM, R , NSM, NSM, R , NSM, ON , ON , ON , ON , ON , ON , ON , ON , + /*D-*/ R , R , R , R , R , R , R , R , R , R , R , R , R , R , R , R , + /*E-*/ R , R , R , R , R , R , R , R , R , R , R , ON , ON , ON , ON , ON , + /*F-*/ R , R , R , R , R , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON + ], + [ /* Table 02: Unicode 06xx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ AN , AN , AN , AN , ON , ON , ON , ON , AL , ET , ET , AL , CS , AL , ON , ON , + /*1-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, AL , ON , ON , AL , AL , + /*2-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*3-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*4-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , NSM, NSM, NSM, NSM, NSM, + /*5-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*6-*/ AN , AN , AN , AN , AN , AN , AN , AN , AN , AN , ET , AN , AN , AL , AL , AL , + /*7-*/ NSM, AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*8-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*9-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*A-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*B-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*C-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*D-*/ AL , AL , AL , AL , AL , AL , NSM, NSM, NSM, NSM, NSM, NSM, NSM, AN , ON , NSM, + /*E-*/ NSM, NSM, NSM, NSM, NSM, AL , AL , NSM, NSM, ON , NSM, NSM, NSM, NSM, AL , AL , + /*F-*/ EN , EN , EN , EN , EN , EN , EN , EN , EN , EN , AL , AL , AL , AL , AL , AL + ], + [ /* Table 03: Unicode 07xx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , ON , AL , + /*1-*/ AL , NSM, AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*2-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*3-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*4-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, ON , ON , AL , AL , AL , + /*5-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*6-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*7-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*8-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*9-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*A-*/ AL , AL , AL , AL , AL , AL , NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*B-*/ NSM, AL , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*C-*/ R , R , R , R , R , R , R , R , R , R , R , R , R , R , R , R , + /*D-*/ R , R , R , R , R , R , R , R , R , R , R , R , R , R , R , R , + /*E-*/ R , R , R , R , R , R , R , R , R , R , R , NSM, NSM, NSM, NSM, NSM, + /*F-*/ NSM, NSM, NSM, NSM, R , R , ON , ON , ON , ON , R , ON , ON , ON , ON , ON + ], + [ /* Table 04: Unicode 20xx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ WS , WS , WS , WS , WS , WS , WS , WS , WS , WS , WS , BN , BN , BN , L , R , + /*1-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*2-*/ ON , ON , ON , ON , ON , ON , ON , ON , WS , B , LRE, RLE, PDF, LRO, RLO, CS , + /*3-*/ ET , ET , ET , ET , ET , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*4-*/ ON , ON , ON , ON , CS , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*5-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , WS , + /*6-*/ BN , BN , BN , BN , BN , ON , ON , ON , ON , ON , BN , BN , BN , BN , BN , BN , + /*7-*/ EN , L , ON , ON , EN , EN , EN , EN , EN , EN , ES , ES , ON , ON , ON , L , + /*8-*/ EN , EN , EN , EN , EN , EN , EN , EN , EN , EN , ES , ES , ON , ON , ON , ON , + /*9-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , ON , ON , ON , + /*A-*/ ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , + /*B-*/ ET , ET , ET , ET , ET , ET , ET , ET , ET , ET , ON , ON , ON , ON , ON , ON , + /*C-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*D-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*E-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, + /*F-*/ NSM, ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON + ], + [ /* Table 05: Unicode FBxx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ L , L , L , L , L , L , L , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*1-*/ ON , ON , ON , L , L , L , L , L , ON , ON , ON , ON , ON , R , NSM, R , + /*2-*/ R , R , R , R , R , R , R , R , R , ES , R , R , R , R , R , R , + /*3-*/ R , R , R , R , R , R , R , ON , R , R , R , R , R , ON , R , ON , + /*4-*/ R , R , ON , R , R , ON , R , R , R , R , R , R , R , R , R , R , + /*5-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*6-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*7-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*8-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*9-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*A-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*B-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*C-*/ AL , AL , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*D-*/ ON , ON , ON , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*E-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*F-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL + ], + [ /* Table 06: Unicode FExx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM, NSM , + /*1-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*2-*/ NSM, NSM, NSM, NSM, NSM, NSM, NSM, ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*3-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*4-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*5-*/ CS , ON , CS , ON , ON , CS , ON , ON , ON , ON , ON , ON , ON , ON , ON , ET , + /*6-*/ ON , ON , ES , ES , ON , ON , ON , ON , ON , ET , ET , ON , ON , ON , ON , ON , + /*7-*/ AL , AL , AL , AL , AL , ON , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*8-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*9-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*A-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*B-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*C-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*D-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*E-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , + /*F-*/ AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , AL , ON , ON , BN + ], + [ /* Table 07: Unicode FFxx */ + /************************************************************************************************************************************/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + /************************************************************************************************************************************/ + /*0-*/ ON , ON , ON , ET , ET , ET , ON , ON , ON , ON , ON , ES , CS , ES , CS , CS , + /*1-*/ EN , EN , EN , EN , EN , EN , EN , EN , EN , EN , CS , ON , ON , ON , ON , ON , + /*2-*/ ON , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*3-*/ L , L , L , L , L , L , L , L , L , L , L , ON , ON , ON , ON , ON , + /*4-*/ ON , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*5-*/ L , L , L , L , L , L , L , L , L , L , L , ON , ON , ON , ON , ON , + /*6-*/ ON , ON , ON , ON , ON , ON , L , L , L , L , L , L , L , L , L , L , + /*7-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*8-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*9-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*A-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , + /*B-*/ L , L , L , L , L , L , L , L , L , L , L , L , L , L , L , ON , + /*C-*/ ON , ON , L , L , L , L , L , L , ON , ON , L , L , L , L , L , L , + /*D-*/ ON , ON , L , L , L , L , L , L , ON , ON , L , L , L , ON , ON , ON , + /*E-*/ ET , ET , ON , ON , ON , ET , ET , ON , ON , ON , ON , ON , ON , ON , ON , ON , + /*F-*/ ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON , ON + ] +]; + +function _computeLevels(chars, levels, len, charTypes) { + var impTab = dir ? impTab_RTL : impTab_LTR + , prevState = null, newClass = null, newLevel = null, newState = 0 + , action = null, cond = null, condPos = -1, i = null, ix = null, classes = []; + + if (!charTypes) { + for (i = 0, charTypes = []; i < len; i++) { + charTypes[i] = _getCharacterType(chars[i]); + } + } + hiLevel = dir; + lastArabic = false; + hasUBAT_AL = false, + hasUBAT_B = false; + hasUBAT_S = false; + for (ix = 0; ix < len; ix++){ + prevState = newState; + classes[ix] = newClass = _getCharClass(chars, charTypes, classes, ix); + newState = impTab[prevState][newClass]; + action = newState & 0xF0; + newState &= 0x0F; + levels[ix] = newLevel = impTab[newState][5]; + if (action > 0){ + if (action == 0x10){ + for(i = condPos; i < ix; i++){ + levels[i] = 1; + } + condPos = -1; + } else { + condPos = -1; + } + } + cond = impTab[newState][6]; + if (cond){ + if(condPos == -1){ + condPos = ix; + } + }else{ + if (condPos > -1){ + for(i = condPos; i < ix; i++){ + levels[i] = newLevel; + } + condPos = -1; + } + } + if (charTypes[ix] == B){ + levels[ix] = 0; + } + hiLevel |= newLevel; + } + if (hasUBAT_S){ + for(i = 0; i < len; i++){ + if(charTypes[i] == S){ + levels[i] = dir; + for(var j = i - 1; j >= 0; j--){ + if(charTypes[j] == WS){ + levels[j] = dir; + }else{ + break; + } + } + } + } + } +} + +function _invertLevel(lev, levels, _array) { + if (hiLevel < lev){ + return; + } + if (lev == 1 && dir == RTL && !hasUBAT_B){ + _array.reverse(); + return; + } + var len = _array.length, start = 0, end, lo, hi, tmp; + while(start < len){ + if (levels[start] >= lev){ + end = start + 1; + while(end < len && levels[end] >= lev){ + end++; + } + for(lo = start, hi = end - 1 ; lo < hi; lo++, hi--){ + tmp = _array[lo]; + _array[lo] = _array[hi]; + _array[hi] = tmp; + } + start = end; + } + start++; + } +} + +function _getCharClass(chars, types, classes, ix) { + var cType = types[ix], wType, nType, len, i; + switch(cType){ + case L: + case R: + lastArabic = false; + case ON: + case AN: + return cType; + case EN: + return lastArabic ? AN : EN; + case AL: + lastArabic = true; + hasUBAT_AL = true; + return R; + case WS: + return ON; + case CS: + if (ix < 1 || (ix + 1) >= types.length || + ((wType = classes[ix - 1]) != EN && wType != AN) || + ((nType = types[ix + 1]) != EN && nType != AN)){ + return ON; + } + if (lastArabic){nType = AN;} + return nType == wType ? nType : ON; + case ES: + wType = ix > 0 ? classes[ix - 1] : B; + if (wType == EN && (ix + 1) < types.length && types[ix + 1] == EN){ + return EN; + } + return ON; + case ET: + if (ix > 0 && classes[ix - 1] == EN){ + return EN; + } + if (lastArabic){ + return ON; + } + i = ix + 1; + len = types.length; + while (i < len && types[i] == ET){ + i++; + } + if (i < len && types[i] == EN){ + return EN; + } + return ON; + case NSM: + len = types.length; + i = ix + 1; + while (i < len && types[i] == NSM){ + i++; + } + if (i < len){ + var c = chars[ix], rtlCandidate = (c >= 0x0591 && c <= 0x08FF) || c == 0xFB1E; + + wType = types[i]; + if (rtlCandidate && (wType == R || wType == AL)){ + return R; + } + } + + if (ix < 1 || (wType = types[ix - 1]) == B){ + return ON; + } + return classes[ix - 1]; + case B: + lastArabic = false; + hasUBAT_B = true; + return dir; + case S: + hasUBAT_S = true; + return ON; + case LRE: + case RLE: + case LRO: + case RLO: + case PDF: + lastArabic = false; + case BN: + return ON; + } +} + +function _getCharacterType( ch ) { + var uc = ch.charCodeAt(0), hi = MasterTable[uc >> 8]; + return (hi < TBBASE) ? hi : UnicodeTable[hi - TBBASE][uc & 0xFF]; +} + +function _isArabicDiacritics( ch ) { + return (ch >= '\u064b' && ch <= '\u0655'); +} + +/* Strong LTR character (0 - even), regular width */ +exports.L = L; +/* Strong RTL character (1 - odd), Bidi width */ +exports.R = R; +/* European digit (2 - even), regular width */ +exports.EN = EN; +/* Neutral RTL-by-context character (3 - odd), regular width */ +exports.ON_R = 3; +/* Hindi (Arabic) digit (4 - even), Bidi width */ +exports.AN = 4; +/* Arabic LamAlef (5 - odd), Half Bidi width */ +exports.R_H = 5; +/* invisible EOL (6 - even), zero width */ +exports.B = 6; + +exports.DOT = "\xB7"; + +/** + * Performs text reordering by implementing Unicode Bidi algorithm + * with aim to produce logical<->visual map and Bidi levels + * @param {String} text string to be reordered + * @param {Array} unicode character types produced by call to 'hasBidiCharacters' + * @param {Boolean} 'true' for right-to-left text direction, otherwise 'false' + * + * @return {Object} An object containing logicalFromVisual map and Bidi levels + **/ +exports.doBidiReorder = function(text, textCharTypes, isRtl) { + if (text.length < 2) + return {}; + + var chars = text.split(""), logicalFromVisual = new Array(chars.length), + bidiLevels = new Array(chars.length), levels = []; + + dir = isRtl ? RTL : LTR; + + _computeLevels(chars, levels, chars.length, textCharTypes); + + for (var i = 0; i < logicalFromVisual.length; logicalFromVisual[i] = i, i++); + + _invertLevel(2, levels, logicalFromVisual); + _invertLevel(1, levels, logicalFromVisual); + + for (var i = 0; i < logicalFromVisual.length - 1; i++) { //fix levels to reflect character width + if (textCharTypes[i] === AN) { + levels[i] = exports.AN; + } else if (levels[i] === R && (textCharTypes[i] > AL || textCharTypes[i] === ON)) { + levels[i] = exports.ON_R; + } else if ((i > 0 && chars[i - 1] === '\u0644') && /\u0622|\u0623|\u0625|\u0627/.test(chars[i])) { + levels[i - 1] = levels[i] = exports.R_H; + i++; + } + } + /* fix level to mark zero length EOL */ + if (chars[chars.length - 1] === exports.DOT) + levels[chars.length - 1] = B; + + for (var i = 0; i < logicalFromVisual.length; i++) { + bidiLevels[i] = levels[logicalFromVisual[i]]; + } + + return {'logicalFromVisual': logicalFromVisual, 'bidiLevels': bidiLevels}; +}; + +/** + * Performs character classification, to be used in Unicode Bidi algorithm. + * @param {String} text string to be reordered + * @param {Array} unicode character types (to be filled by this method) + * + * @return {Boolean} 'true' if text contains Bidi characters, otherwise 'false' + **/ +exports.hasBidiCharacters = function(text, textCharTypes){ + var ret = false; + for (var i = 0; i < text.length; i++){ + textCharTypes[i] = _getCharacterType(text.charAt(i)); + if (!ret && (textCharTypes[i] == R || textCharTypes[i] == AL)) + ret = true; + } + return ret; +}; + +/** + * Returns visual index corresponding to logical index basing on logicalFromvisual + * map provided by Unicode Bidi algorithm. + * @param {int} logical index of character in text buffer + * @param {Object} object containing logicalFromVisual map + * + * @return {int} visual index (on display) corresponding to logical index + **/ +exports.getVisualFromLogicalIdx = function(logIdx, rowMap) { + for (var i = 0; i < rowMap.logicalFromVisual.length; i++) { + if (rowMap.logicalFromVisual[i] == logIdx) + return i; + } + return 0; +}; + +}); diff --git a/lib/ace/mouse/multi_select_handler.js b/lib/ace/mouse/multi_select_handler.js index 649043faee4..d09ab27abb2 100644 --- a/lib/ace/mouse/multi_select_handler.js +++ b/lib/ace/mouse/multi_select_handler.js @@ -145,7 +145,7 @@ function onMouseDown(e) { var rectSel = []; var blockSelect = function() { var newCursor = editor.renderer.pixelToScreenCoordinates(mouseX, mouseY); - var cursor = session.screenToDocumentPosition(newCursor.row, newCursor.column); + var cursor = session.screenToDocumentPosition(newCursor.row, newCursor.column, newCursor.offsetX); if (isSamePoint(screenCursor, newCursor) && isSamePoint(cursor, selection.lead)) return; diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index e37b7e24bf5..32f2da69dc4 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -281,9 +281,13 @@ var EditSession = require("./edit_session").EditSession; if (xBackwards) { var startColumn = screenCursor.column; var endColumn = screenAnchor.column; + var startOffsetX = screenCursor.offsetX; + var endOffsetX = screenAnchor.offsetX; } else { var startColumn = screenAnchor.column; var endColumn = screenCursor.column; + var startOffsetX = screenAnchor.offsetX; + var endOffsetX = screenCursor.offsetX; } var yBackwards = screenCursor.row < screenAnchor.row; @@ -305,8 +309,8 @@ var EditSession = require("./edit_session").EditSession; for (var row = startRow; row <= endRow; row++) { var range = Range.fromPoints( - this.session.screenToDocumentPosition(row, startColumn), - this.session.screenToDocumentPosition(row, endColumn) + this.session.screenToDocumentPosition(row, startColumn, startOffsetX), + this.session.screenToDocumentPosition(row, endColumn, endOffsetX) ); if (range.isEmpty()) { if (docEnd && isSamePoint(range.end, docEnd)) diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index bb003aade91..5187baf915d 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -294,6 +294,7 @@ var VirtualRenderer = function(container, theme) { this.onChangeNewLineMode = function() { this.$loop.schedule(this.CHANGE_TEXT); this.$textLayer.$updateEolChar(); + this.session.$bidiHandler.setEolChar(this.$textLayer.EOL_CHAR); }; this.onChangeTabSize = function() { @@ -480,6 +481,7 @@ var VirtualRenderer = function(container, theme) { **/ this.setShowInvisibles = function(showInvisibles) { this.setOption("showInvisibles", showInvisibles); + this.session.$bidiHandler.setShowInvisibles(showInvisibles); }; /** @@ -854,6 +856,11 @@ var VirtualRenderer = function(container, theme) { this.$markerBack.update(config); this.$markerFront.update(config); this.$cursorLayer.update(config); + + this.session.$bidiHandler.updateCharacterWidths(useragent.isChrome, this.$fontMetrics); + if (this.session.$bidiHandler.getShowInvisibles() === undefined) + this.session.$bidiHandler.setShowInvisibles(this.getShowInvisibles()); + this.$moveTextAreaToCursor(); this.$highlightGutterLine && this.$updateGutterLineHighlight(); this._signal("afterRender"); @@ -1405,23 +1412,23 @@ var VirtualRenderer = function(container, theme) { this.pixelToScreenCoordinates = function(x, y) { var canvasPos = this.scroller.getBoundingClientRect(); - var offset = (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth; + var offsetX = x + this.scrollLeft - canvasPos.left - this.$padding; + var offset = offsetX / this.characterWidth; var row = Math.floor((y + this.scrollTop - canvasPos.top) / this.lineHeight); var col = Math.round(offset); - return {row: row, column: col, side: offset - col > 0 ? 1 : -1}; + return {row: row, column: col, side: offset - col > 0 ? 1 : -1, offsetX: offsetX}; }; this.screenToTextCoordinates = function(x, y) { var canvasPos = this.scroller.getBoundingClientRect(); - - var col = Math.round( - (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth - ); + var offsetX = x + this.scrollLeft - canvasPos.left - this.$padding; + + var col = Math.round(offsetX / this.characterWidth); var row = (y + this.scrollTop - canvasPos.top) / this.lineHeight; - return this.session.screenToDocumentPosition(row, Math.max(col, 0)); + return this.session.screenToDocumentPosition(row, Math.max(col, 0), offsetX); }; /** @@ -1435,7 +1442,10 @@ var VirtualRenderer = function(container, theme) { var canvasPos = this.scroller.getBoundingClientRect(); var pos = this.session.documentToScreenPosition(row, column); - var x = this.$padding + Math.round(pos.column * this.characterWidth); + var x = this.$padding + (this.session.$bidiHandler.isBidiRow(pos.row, row) ? + this.session.$bidiHandler.getPosLeft(pos.column) : + Math.round(pos.column * this.characterWidth)); + var y = pos.row * this.lineHeight; return { From 41d4c522817662e405b77a334d2fb2a0f0241bc8 Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Tue, 8 Nov 2016 20:24:32 +0200 Subject: [PATCH 02/10] fix syntax errors --- lib/ace/bidihandler.js | 28 ++++++------ lib/ace/edit_session.js | 4 +- lib/ace/layer/cursor.js | 4 +- lib/ace/layer/marker.js | 88 ++++++++++++++++++------------------- lib/ace/layer/text.js | 26 +++++------ lib/ace/lib/bidiutil.js | 2 +- lib/ace/virtual_renderer.js | 6 +-- 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index f0951114df8..87824e2fb99 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -122,20 +122,20 @@ var BidiHandler = function(session) { this.wrapIndent = 0; this.line = this.session.getLine(docRow); if (this.session.$useWrapMode) { - var splits = this.session.$wrapData[docRow]; - if (splits) { - if (splitIndex === undefined) - splitIndex = this.getSplitIndex(); - - if(splitIndex > 0 && splits.length) { - this.wrapIndent = splits.indent; - this.line = (splitIndex < splits.length) ? - this.line.substring(splits[splitIndex - 1], splits[splits.length - 1]) : - this.line.substring(splits[splits.length - 1]); - } else { - this.line = this.line.substring(0, splits[splitIndex]); - } - } + var splits = this.session.$wrapData[docRow]; + if (splits) { + if (splitIndex === undefined) + splitIndex = this.getSplitIndex(); + + if(splitIndex > 0 && splits.length) { + this.wrapIndent = splits.indent; + this.line = (splitIndex < splits.length) ? + this.line.substring(splits[splitIndex - 1], splits[splits.length - 1]) : + this.line.substring(splits[splits.length - 1]); + } else { + this.line = this.line.substring(0, splits[splitIndex]); + } + } } /* replace tab and wide characters by commensurate spaces */ diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index e9770a468f1..f02e5527070 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -2224,7 +2224,7 @@ var EditSession = function(text, mode) { } if (offsetX !== undefined && this.$bidiHandler.isBidiRow(row + splitIndex, docRow, splitIndex)) - screenColumn = this.$bidiHandler.offsetToCol(offsetX); + screenColumn = this.$bidiHandler.offsetToCol(offsetX); docColumn += this.$getStringScreenWidth(line, screenColumn - wrapIndent)[1]; @@ -2441,7 +2441,7 @@ var EditSession = function(text, mode) { }; this.isFullWidth = function(c) { - return isFullWidth(c); + return isFullWidth(c); }; // For every keystroke this gets called once per char in the whole doc!! diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index b3f1e9154c7..bddc4e7578e 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -171,8 +171,8 @@ var Cursor = function(parentEl) { position = this.session.selection.getCursor(); var pos = this.session.documentToScreenPosition(position); var cursorLeft = this.$padding + (this.session.$bidiHandler.isBidiRow(pos.row, position.row) ? - this.session.$bidiHandler.getPosLeft(pos.column) : - pos.column * this.config.characterWidth); + this.session.$bidiHandler.getPosLeft(pos.column) : + pos.column * this.config.characterWidth); var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) * this.config.lineHeight; diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 2641c7763ba..96ffddc1bcb 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -79,8 +79,8 @@ var Marker = function(parentEl) { if (marker.renderer) { var top = this.$getTop(range.start.row, config); var left = this.$padding + (this.session.$bidiHandler.isBidiRow(range.start.row) ? - this.session.$bidiHandler.getPosLeft(range.start.column) : - range.start.column * config.characterWidth); + this.session.$bidiHandler.getPosLeft(range.start.column) : + range.start.column * config.characterWidth); marker.renderer(html, range, left, top, config); } else if (marker.type == "fullLine") { @@ -94,9 +94,9 @@ var Marker = function(parentEl) { this.drawMultiLineMarker(html, range, marker.clazz, config); } else { if (this.session.$bidiHandler.isBidiRow(range.start.row)) { - this.drawBidiSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); + this.drawBidiSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); } else { - this.drawSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); + this.drawSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); } } } @@ -129,14 +129,14 @@ var Marker = function(parentEl) { curr = next; next = row + 1 < end ? session.getScreenLastRowColumn(row + 1) : row == end ? 0 : range.end.column; clazzModified = clazz + (row == start ? " ace_start" : "") + " ace_br" - + getBorderClass(row == start || row == start + 1 && range.start.column, prev < curr, curr > next, row == end); + + getBorderClass(row == start || row == start + 1 && range.start.column, prev < curr, curr > next, row == end); if (this.session.$bidiHandler.isBidiRow(row)) { - this.drawBidiSingleLineMarker(stringBuilder, lineRange, clazzModified, - layerConfig, row == end ? 0 : 1, extraStyle); + this.drawBidiSingleLineMarker(stringBuilder, lineRange, clazzModified, + layerConfig, row == end ? 0 : 1, extraStyle); } else { - this.drawSingleLineMarker(stringBuilder, lineRange, clazzModified, - layerConfig, row == end ? 0 : 1, extraStyle); + this.drawSingleLineMarker(stringBuilder, lineRange, clazzModified, + layerConfig, row == end ? 0 : 1, extraStyle); } } }; @@ -148,39 +148,39 @@ var Marker = function(parentEl) { var height, top, left; extraStyle = extraStyle || ""; if (this.session.$bidiHandler.isBidiRow(range.start.row)) { - var range1 = range.clone(); - range1.end.row = range1.start.row; - range1.end.column = this.session.getLine(range1.start.row).length; - this.drawBidiSingleLineMarker(stringBuilder, range1, clazz + " ace_br1 ace_start", config, null, extraStyle); + var range1 = range.clone(); + range1.end.row = range1.start.row; + range1.end.column = this.session.getLine(range1.start.row).length; + this.drawBidiSingleLineMarker(stringBuilder, range1, clazz + " ace_br1 ace_start", config, null, extraStyle); } else { - height = config.lineHeight; - top = this.$getTop(range.start.row, config); - left = padding + range.start.column * config.characterWidth; - stringBuilder.push( - "
" - ); + height = config.lineHeight; + top = this.$getTop(range.start.row, config); + left = padding + range.start.column * config.characterWidth; + stringBuilder.push( + "
" + ); } // from start of the last line to the selection end if (this.session.$bidiHandler.isBidiRow(range.end.row)) { - var range1 = range.clone(); - range1.start.row = range1.end.row; - range1.start.column = 0; - this.drawBidiSingleLineMarker(stringBuilder, range1, clazz + " ace_br12", config, null, extraStyle); + var range1 = range.clone(); + range1.start.row = range1.end.row; + range1.start.column = 0; + this.drawBidiSingleLineMarker(stringBuilder, range1, clazz + " ace_br12", config, null, extraStyle); } else { - var width = range.end.column * config.characterWidth; - height = config.lineHeight; - top = this.$getTop(range.end.row, config); - stringBuilder.push( - "
" - ); + var width = range.end.column * config.characterWidth; + height = config.lineHeight; + top = this.$getTop(range.end.row, config); + stringBuilder.push( + "
" + ); } // all the complete lines height = (range.end.row - range.start.row - 1) * config.lineHeight; @@ -222,13 +222,13 @@ var Marker = function(parentEl) { var selections = this.session.$bidiHandler.getSelections(range.start.column, range.end.column); selections.forEach(function(selection) { - stringBuilder.push( - "
" - ); + stringBuilder.push( + "
" + ); }); }; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index e07e3c08ff6..8408f249c9d 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -206,7 +206,7 @@ var Text = function(parentEl) { lineElement.innerHTML = html.join(""); if (this.session.$bidiHandler.isCourierFontFace()) { lineElement.style.fontFamily = this.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? - "Courier" : ""; + "Courier" : ""; } } row++; @@ -272,7 +272,7 @@ var Text = function(parentEl) { container.innerHTML = html.join(""); if (this.session.$bidiHandler.isCourierFontFace()) - this.setFontFamily(container, this, false); + this.setFontFamily(container, this, false); if (this.$useLineGroups()) { container.className = 'ace_line_group'; @@ -320,20 +320,20 @@ var Text = function(parentEl) { } this.element.innerHTML = html.join(""); if (this.session.$bidiHandler.isCourierFontFace()) - this.setFontFamily(this.element, this, this.$useLineGroups()); + this.setFontFamily(this.element, this, this.$useLineGroups()); }; this.setFontFamily = function(element, self, isLineGroup) { - element.childNodes.forEach(function(lineElement) { - if (lineElement) { - if (isLineGroup) { - self.setFontFamily(lineElement, self, false); - } else { - lineElement.style.fontFamily = self.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? - "Courier" : ""; - } - } - }); + element.childNodes.forEach(function(lineElement) { + if (lineElement) { + if (isLineGroup) { + self.setFontFamily(lineElement, self, false); + } else { + lineElement.style.fontFamily = self.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? + "Courier" : ""; + } + } + }); }; this.$textToken = { diff --git a/lib/ace/lib/bidiutil.js b/lib/ace/lib/bidiutil.js index 0a9a0339a2b..068bf45f2a5 100644 --- a/lib/ace/lib/bidiutil.js +++ b/lib/ace/lib/bidiutil.js @@ -283,7 +283,7 @@ function _computeLevels(chars, levels, len, charTypes) { } hiLevel = dir; lastArabic = false; - hasUBAT_AL = false, + hasUBAT_AL = false; hasUBAT_B = false; hasUBAT_S = false; for (ix = 0; ix < len; ix++){ diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 5187baf915d..f15b9cf666a 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -859,7 +859,7 @@ var VirtualRenderer = function(container, theme) { this.session.$bidiHandler.updateCharacterWidths(useragent.isChrome, this.$fontMetrics); if (this.session.$bidiHandler.getShowInvisibles() === undefined) - this.session.$bidiHandler.setShowInvisibles(this.getShowInvisibles()); + this.session.$bidiHandler.setShowInvisibles(this.getShowInvisibles()); this.$moveTextAreaToCursor(); this.$highlightGutterLine && this.$updateGutterLineHighlight(); @@ -1443,8 +1443,8 @@ var VirtualRenderer = function(container, theme) { var pos = this.session.documentToScreenPosition(row, column); var x = this.$padding + (this.session.$bidiHandler.isBidiRow(pos.row, row) ? - this.session.$bidiHandler.getPosLeft(pos.column) : - Math.round(pos.column * this.characterWidth)); + this.session.$bidiHandler.getPosLeft(pos.column) : + Math.round(pos.column * this.characterWidth)); var y = pos.row * this.lineHeight; From 8d5044a6e12bf358ca8a49b5e2580930e1dadb25 Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Wed, 9 Nov 2016 17:30:17 +0200 Subject: [PATCH 03/10] fix undo related selection problem --- lib/ace/bidihandler.js | 7 +++++-- lib/ace/edit_session.js | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index 87824e2fb99..d3e9d150e20 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -61,6 +61,8 @@ var BidiHandler = function(session) { this.wrapIndent = 0; /* font face has been set to Courier to ensure monospaced Bidi characters in Chrome/Opera */ this.isCourierFont = false; + this.isLastRow = false; + this.EOF = "\xB6"; }; (function() { @@ -120,6 +122,7 @@ var BidiHandler = function(session) { docRow = this.getDocumentRow(); this.wrapIndent = 0; + this.isLastRow = (docRow === this.session.getLength() - 1); this.line = this.session.getLine(docRow); if (this.session.$useWrapMode) { var splits = this.session.$wrapData[docRow]; @@ -150,8 +153,8 @@ var BidiHandler = function(session) { }; this.updateBidiMap = function() { - var textCharTypes = []; - var line = this.line + (this.showInvisibles ? this.EOL : bidiUtil.DOT); + var textCharTypes = [], endOfLine = this.isLastRow ? this.EOF : this.EOL; + var line = this.line + (this.showInvisibles ? endOfLine : bidiUtil.DOT); if (bidiUtil.hasBidiCharacters(line, textCharTypes)) { this.bidiMap = bidiUtil.doBidiReorder(line, textCharTypes, this.isRtlDir); } else { diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index f02e5527070..43dd52c1a97 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1145,6 +1145,7 @@ var EditSession = function(text, mode) { * **/ this.removeFullLines = function(firstRow, lastRow){ + this.$bidiHandler.markAsDirty(); return this.doc.removeFullLines(firstRow, lastRow); }; @@ -1178,6 +1179,8 @@ var EditSession = function(text, mode) { this.$undoSelect && !dontSelect && this.selection.setSelectionRange(lastUndoRange); + + this.$bidiHandler.markAsDirty(); return lastUndoRange; }; From c18dde669199c1dfaf2573748c38aa823bf310bb Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Mon, 6 Feb 2017 21:10:17 +0200 Subject: [PATCH 04/10] fix up/down arrows problem --- lib/ace/bidihandler.js | 3 ++- lib/ace/selection.js | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index d3e9d150e20..f824624cd66 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -33,6 +33,7 @@ define(function(require, exports, module) { var bidiUtil = require("./lib/bidiutil"); var lang = require("./lib/lang"); +var useragent = require("./lib/useragent"); /** * This object is used to ensure Bi-Directional support (for languages with text flowing from right to left, like Arabic or Hebrew) @@ -178,7 +179,7 @@ var BidiHandler = function(session) { this.updateCharacterWidths = function(isChrome, $fontMetrics) { var _regularCharWidth = $fontMetrics.$measureCharWidth('a'), _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); /* on some Windows7 OS not 'Monaco' but 'Courier' family is true monospaced font for Bidi characters in Chrome based browsers */ - if (isChrome && (_bidiCharWidth !== $fontMetrics.$measureCharWidth('\u05d5'))) { + if (!useragent.isMac && isChrome && (_bidiCharWidth !== $fontMetrics.$measureCharWidth('\u05d5'))) { this.isCourierFont = true; $fontMetrics.$main.style.fontFamily = "Courier"; _regularCharWidth = $fontMetrics.$measureCharWidth('a'); diff --git a/lib/ace/selection.js b/lib/ace/selection.js index ff6cb335d07..03047c58c76 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -789,15 +789,21 @@ var Selection = function(session) { this.lead.column ); + var offsetX; //undefined if (chars === 0) { + if (rows !== 0) { + offsetX = this.session.$bidiHandler.isBidiRow(screenPos.row, this.lead.row) ? + this.session.$bidiHandler.getPosLeft(screenPos.column) : + screenPos.column * this.session.$bidiHandler.charWidths[0]; + } + if (this.$desiredColumn) screenPos.column = this.$desiredColumn; else this.$desiredColumn = screenPos.column; } - var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenPos.column); - + var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenPos.column, offsetX); if (rows !== 0 && chars === 0 && docPos.row === this.lead.row && docPos.column === this.lead.column) { if (this.session.lineWidgets && this.session.lineWidgets[docPos.row]) { if (docPos.row > 0 || rows > 0) From 83153ff2002d53ceecbdc73a51ba2809bd68dfa4 Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Tue, 7 Feb 2017 20:23:18 +0200 Subject: [PATCH 05/10] improve fix for up/down arrows --- lib/ace/bidihandler.js | 7 +++---- lib/ace/selection.js | 10 +++++++--- lib/ace/virtual_renderer.js | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index f824624cd66..aa208518c8f 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -172,14 +172,13 @@ var BidiHandler = function(session) { /** * Updates array of character widths - * @param {boolena} is chrome based engine * @param {Object} font metrics * **/ - this.updateCharacterWidths = function(isChrome, $fontMetrics) { + this.updateCharacterWidths = function($fontMetrics) { var _regularCharWidth = $fontMetrics.$measureCharWidth('a'), _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); /* on some Windows7 OS not 'Monaco' but 'Courier' family is true monospaced font for Bidi characters in Chrome based browsers */ - if (!useragent.isMac && isChrome && (_bidiCharWidth !== $fontMetrics.$measureCharWidth('\u05d5'))) { + if (!useragent.isMac && useragent.isChrome && (_bidiCharWidth !== $fontMetrics.$measureCharWidth('\u05d5'))) { this.isCourierFont = true; $fontMetrics.$main.style.fontFamily = "Courier"; _regularCharWidth = $fontMetrics.$measureCharWidth('a'); @@ -189,7 +188,7 @@ var BidiHandler = function(session) { this.charWidths[bidiUtil.L] = this.charWidths[bidiUtil.EN] = this.charWidths[bidiUtil.ON_R] = _regularCharWidth; this.charWidths[bidiUtil.R] = this.charWidths[bidiUtil.AN] = _bidiCharWidth; - this.charWidths[bidiUtil.R_H] = isChrome ? _bidiCharWidth : _bidiCharWidth * 0.45; + this.charWidths[bidiUtil.R_H] = useragent.isChrome ? _bidiCharWidth : _bidiCharWidth * 0.45; this.charWidths[bidiUtil.B] = 0; if (this.currentRow !== null) { diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 03047c58c76..e37bb787410 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -790,11 +790,15 @@ var Selection = function(session) { ); var offsetX; //undefined + if (chars === 0) { if (rows !== 0) { - offsetX = this.session.$bidiHandler.isBidiRow(screenPos.row, this.lead.row) ? - this.session.$bidiHandler.getPosLeft(screenPos.column) : - screenPos.column * this.session.$bidiHandler.charWidths[0]; + if (this.session.$bidiHandler.isBidiRow(screenPos.row, this.lead.row)) { + offsetX = this.session.$bidiHandler.getPosLeft(screenPos.column); + screenPos.column = Math.round(offsetX / this.session.$bidiHandler.charWidths[0]); + } else { + offsetX = screenPos.column * this.session.$bidiHandler.charWidths[0]; + } } if (this.$desiredColumn) diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index f15b9cf666a..a575b977ce0 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -857,7 +857,7 @@ var VirtualRenderer = function(container, theme) { this.$markerFront.update(config); this.$cursorLayer.update(config); - this.session.$bidiHandler.updateCharacterWidths(useragent.isChrome, this.$fontMetrics); + this.session.$bidiHandler.updateCharacterWidths(this.$fontMetrics); if (this.session.$bidiHandler.getShowInvisibles() === undefined) this.session.$bidiHandler.setShowInvisibles(this.getShowInvisibles()); From 3567657f44ba5ebe7987d0d950a6661194921cb8 Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Mon, 13 Feb 2017 20:07:27 +0200 Subject: [PATCH 06/10] fix width of EOL --- lib/ace/bidihandler.js | 2 +- lib/ace/lib/bidiutil.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index aa208518c8f..b3fea396d1a 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -144,7 +144,7 @@ var BidiHandler = function(session) { /* replace tab and wide characters by commensurate spaces */ var session = this.session, shift = 0, size; - this.line = this.line.replace(/\t|[\u1100-\uFFE6]/g, function(ch, i){ + this.line = this.line.replace(/\t|[\u1100-\u2029, \u202F-\uFFE6]/g, function(ch, i){ if (ch === '\t' || session.isFullWidth(ch.charCodeAt(0))) { size = (ch === '\t') ? session.getScreenTabSize(i + shift) : 2; shift += size - 1; diff --git a/lib/ace/lib/bidiutil.js b/lib/ace/lib/bidiutil.js index 068bf45f2a5..b04b0607b4d 100644 --- a/lib/ace/lib/bidiutil.js +++ b/lib/ace/lib/bidiutil.js @@ -501,7 +501,8 @@ exports.doBidiReorder = function(text, textCharTypes, isRtl) { for (var i = 0; i < logicalFromVisual.length - 1; i++) { //fix levels to reflect character width if (textCharTypes[i] === AN) { levels[i] = exports.AN; - } else if (levels[i] === R && (textCharTypes[i] > AL || textCharTypes[i] === ON)) { + } else if (levels[i] === R && ((textCharTypes[i] > AL && textCharTypes[i] < LRE) + || textCharTypes[i] === ON || textCharTypes[i] === BN)) { levels[i] = exports.ON_R; } else if ((i > 0 && chars[i - 1] === '\u0644') && /\u0622|\u0623|\u0625|\u0627/.test(chars[i])) { levels[i - 1] = levels[i] = exports.R_H; @@ -510,7 +511,7 @@ exports.doBidiReorder = function(text, textCharTypes, isRtl) { } /* fix level to mark zero length EOL */ if (chars[chars.length - 1] === exports.DOT) - levels[chars.length - 1] = B; + levels[chars.length - 1] = exports.B; for (var i = 0; i < logicalFromVisual.length; i++) { bidiLevels[i] = levels[logicalFromVisual[i]]; From 8b21722f4dff1e43a5c4debf066a54b0bcf6a9d3 Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Mon, 13 Feb 2017 20:24:42 +0200 Subject: [PATCH 07/10] fix width of EOL and more --- lib/ace/bidihandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index b3fea396d1a..208a6df9ad3 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -150,6 +150,7 @@ var BidiHandler = function(session) { shift += size - 1; return lang.stringRepeat(bidiUtil.DOT, size); } + return ch; }); }; From 99b8abc6489fb67f3f6a97fdf4ac47ccb172edfb Mon Sep 17 00:00:00 2001 From: Alex Shensis Date: Wed, 14 Jun 2017 21:15:07 +0300 Subject: [PATCH 08/10] address review comments 1 --- lib/ace/bidihandler.js | 28 +---- lib/ace/edit_session.js | 10 +- lib/ace/layer/text.js | 23 ---- lib/ace/lib/bidiutil.js | 238 ++++++++-------------------------------- 4 files changed, 51 insertions(+), 248 deletions(-) diff --git a/lib/ace/bidihandler.js b/lib/ace/bidihandler.js index 208a6df9ad3..73acbba5b2f 100644 --- a/lib/ace/bidihandler.js +++ b/lib/ace/bidihandler.js @@ -60,8 +60,6 @@ var BidiHandler = function(session) { this.isRtlDir = false; this.line = ""; this.wrapIndent = 0; - /* font face has been set to Courier to ensure monospaced Bidi characters in Chrome/Opera */ - this.isCourierFont = false; this.isLastRow = false; this.EOF = "\xB6"; }; @@ -74,7 +72,7 @@ var BidiHandler = function(session) { * @param {Number} the screen row to be checked * @param {Number} the document row to be checked [optional] * @param {Number} the wrapped screen line index [ optional] - **/ + **/ this.isBidiRow = function(screenRow, docRow, splitIndex) { if (screenRow !== this.currentRow) { this.currentRow = screenRow; @@ -84,10 +82,6 @@ var BidiHandler = function(session) { return this.bidiMap.bidiLevels; }; - this.hasBidiCharacters = function(str) { - return /[\u0591-\u06ff\ufb1d-\ufefc]/.test(str); - }; - this.getDocumentRow = function() { var docRow = 0; var rowCache = this.session.$screenRowCache; @@ -166,7 +160,7 @@ var BidiHandler = function(session) { /** * Resets stored info related to current screen row - **/ + **/ this.markAsDirty = function() { this.currentRow = null; }; @@ -175,17 +169,9 @@ var BidiHandler = function(session) { * Updates array of character widths * @param {Object} font metrics * - **/ + **/ this.updateCharacterWidths = function($fontMetrics) { var _regularCharWidth = $fontMetrics.$measureCharWidth('a'), _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); - /* on some Windows7 OS not 'Monaco' but 'Courier' family is true monospaced font for Bidi characters in Chrome based browsers */ - if (!useragent.isMac && useragent.isChrome && (_bidiCharWidth !== $fontMetrics.$measureCharWidth('\u05d5'))) { - this.isCourierFont = true; - $fontMetrics.$main.style.fontFamily = "Courier"; - _regularCharWidth = $fontMetrics.$measureCharWidth('a'); - _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); - $fontMetrics.$main.style.fontFamily = "inherit"; - } this.charWidths[bidiUtil.L] = this.charWidths[bidiUtil.EN] = this.charWidths[bidiUtil.ON_R] = _regularCharWidth; this.charWidths[bidiUtil.R] = this.charWidths[bidiUtil.AN] = _bidiCharWidth; @@ -197,10 +183,6 @@ var BidiHandler = function(session) { } }; - this.isCourierFontFace = function() { - return this.isCourierFont; - }; - this.getShowInvisibles = function() { return this.showInvisibles; }; @@ -253,7 +235,7 @@ var BidiHandler = function(session) { * @param {Number} the end column position * * @return {Array of Objects} Each object contains 'left' and 'width' values defining selection rectangle. - **/ + **/ this.getSelections = function(startCol, endCol) { var map = this.bidiMap, levels = map.bidiLevels, level, offset = this.wrapIndent * this.charWidths[bidiUtil.L], selections = [], selColMin = Math.min(startCol, endCol) - this.wrapIndent, selColMax = Math.max(startCol, endCol) - this.wrapIndent, @@ -284,7 +266,7 @@ var BidiHandler = function(session) { * @param {int} character horizontal offset * * @return {Number} screen column number corresponding to given pixel offset - **/ + **/ this.offsetToCol = function(posX) { var logicalIdx = 0, posX = Math.max(posX, 0), offset = 0, visualIdx = 0, levels = this.bidiMap.bidiLevels, diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 43dd52c1a97..928350895db 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -253,7 +253,7 @@ var EditSession = function(text, mode) { this.onChange = function(delta) { this.$modified = true; - + this.$bidiHandler.markAsDirty(); this.$resetRowCache(delta.start.row); var removedFolds = this.$updateInternalDataOnChange(delta); @@ -1118,7 +1118,6 @@ var EditSession = function(text, mode) { * **/ this.insert = function(position, text) { - this.$bidiHandler.markAsDirty(); return this.doc.insert(position, text); }; @@ -1131,7 +1130,6 @@ var EditSession = function(text, mode) { * **/ this.remove = function(range) { - this.$bidiHandler.markAsDirty(); return this.doc.remove(range); }; @@ -1145,7 +1143,6 @@ var EditSession = function(text, mode) { * **/ this.removeFullLines = function(firstRow, lastRow){ - this.$bidiHandler.markAsDirty(); return this.doc.removeFullLines(firstRow, lastRow); }; @@ -1180,7 +1177,6 @@ var EditSession = function(text, mode) { !dontSelect && this.selection.setSelectionRange(lastUndoRange); - this.$bidiHandler.markAsDirty(); return lastUndoRange; }; @@ -2443,9 +2439,7 @@ var EditSession = function(text, mode) { this.$stopWorker(); }; - this.isFullWidth = function(c) { - return isFullWidth(c); - }; + this.isFullWidth = isFullWidth; // For every keystroke this gets called once per char in the whole doc!! // Wouldn't hurt to make it a bit faster for c >= 0x1100 diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 8408f249c9d..7ed633e2424 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -204,10 +204,6 @@ var Text = function(parentEl) { ); lineElement.style.height = config.lineHeight * this.session.getRowLength(row) + "px"; lineElement.innerHTML = html.join(""); - if (this.session.$bidiHandler.isCourierFontFace()) { - lineElement.style.fontFamily = this.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? - "Courier" : ""; - } } row++; } @@ -271,9 +267,6 @@ var Text = function(parentEl) { // don't use setInnerHtml since we are working with an empty DIV container.innerHTML = html.join(""); - if (this.session.$bidiHandler.isCourierFontFace()) - this.setFontFamily(container, this, false); - if (this.$useLineGroups()) { container.className = 'ace_line_group'; fragment.appendChild(container); @@ -319,21 +312,6 @@ var Text = function(parentEl) { row++; } this.element.innerHTML = html.join(""); - if (this.session.$bidiHandler.isCourierFontFace()) - this.setFontFamily(this.element, this, this.$useLineGroups()); - }; - - this.setFontFamily = function(element, self, isLineGroup) { - element.childNodes.forEach(function(lineElement) { - if (lineElement) { - if (isLineGroup) { - self.setFontFamily(lineElement, self, false); - } else { - lineElement.style.fontFamily = self.session.$bidiHandler.hasBidiCharacters(lineElement.textContent) ? - "Courier" : ""; - } - } - }); }; this.$textToken = { @@ -486,7 +464,6 @@ var Text = function(parentEl) { else var tokens = this.session.getTokens(row); - if (!onlyContents) { stringBuilder.push( "
= 0) + docRow = this.session.$docRowCache[index]; + } + + return docRow; + }; + + this.getSplitIndex = function() { + var splitIndex = 0; + var rowCache = this.session.$screenRowCache; + if (rowCache.length) { + var currentIndex, prevIndex = this.session.$getRowCacheIndex(rowCache, this.currentRow); + while (this.currentRow - splitIndex > 0) { + currentIndex = this.session.$getRowCacheIndex(rowCache, this.currentRow - splitIndex - 1); + if (currentIndex !== prevIndex) + break; + + prevIndex = currentIndex; + splitIndex++; + } + } + + return splitIndex; + }; + + this.updateRowLine = function(docRow, splitIndex) { + if (docRow === undefined) + docRow = this.getDocumentRow(); + + this.wrapIndent = 0; + this.isLastRow = (docRow === this.session.getLength() - 1); + this.line = this.session.getLine(docRow); + if (this.session.$useWrapMode) { + var splits = this.session.$wrapData[docRow]; + if (splits) { + if (splitIndex === undefined) + splitIndex = this.getSplitIndex(); + + if(splitIndex > 0 && splits.length) { + this.wrapIndent = splits.indent; + this.line = (splitIndex < splits.length) ? + this.line.substring(splits[splitIndex - 1], splits[splits.length - 1]) : + this.line.substring(splits[splits.length - 1]); + } else { + this.line = this.line.substring(0, splits[splitIndex]); + } + } + } + + /* replace tab and wide characters by commensurate spaces */ + var session = this.session, shift = 0, size; + this.line = this.line.replace(/\t|[\u1100-\u2029, \u202F-\uFFE6]/g, function(ch, i){ + if (ch === '\t' || session.isFullWidth(ch.charCodeAt(0))) { + size = (ch === '\t') ? session.getScreenTabSize(i + shift) : 2; + shift += size - 1; + return lang.stringRepeat(bidiUtil.DOT, size); + } + return ch; + }); + }; + + this.updateBidiMap = function() { + var textCharTypes = [], endOfLine = this.isLastRow ? this.EOF : this.EOL; + var line = this.line + (this.showInvisibles ? endOfLine : bidiUtil.DOT); + if (bidiUtil.hasBidiCharacters(line, textCharTypes)) { + this.bidiMap = bidiUtil.doBidiReorder(line, textCharTypes, this.isRtlDir); + } else { + this.bidiMap = {}; + } + }; /** * Resets stored info related to current screen row **/ - this.markAsDirty = function() { - this.currentRow = null; - }; + this.markAsDirty = function() { + this.currentRow = null; + }; /** * Updates array of character widths * @param {Object} font metrics * **/ - this.updateCharacterWidths = function($fontMetrics) { - var _regularCharWidth = $fontMetrics.$measureCharWidth('a'), _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); - - this.charWidths[bidiUtil.L] = this.charWidths[bidiUtil.EN] = this.charWidths[bidiUtil.ON_R] = _regularCharWidth; - this.charWidths[bidiUtil.R] = this.charWidths[bidiUtil.AN] = _bidiCharWidth; - this.charWidths[bidiUtil.R_H] = useragent.isChrome ? _bidiCharWidth : _bidiCharWidth * 0.45; - this.charWidths[bidiUtil.B] = 0; - - if (this.currentRow !== null) { - this.updateBidiMap(); - } - }; - - this.getShowInvisibles = function() { - return this.showInvisibles; - }; - - this.setShowInvisibles = function(showInvisibles) { - this.showInvisibles = showInvisibles; - if (this.currentRow !== null) { - this.updateBidiMap(); - } - }; - - this.setEolChar = function(eolChar) { - this.EOL = eolChar; - }; - - this.setTextDir = function(isRtlDir) { - this.isRtlDir = isRtlDir; - }; + this.updateCharacterWidths = function($fontMetrics) { + var _regularCharWidth = $fontMetrics.$measureCharWidth('a'), _bidiCharWidth = $fontMetrics.$measureCharWidth('\u05d4'); + + this.charWidths[bidiUtil.L] = this.charWidths[bidiUtil.EN] = this.charWidths[bidiUtil.ON_R] = _regularCharWidth; + this.charWidths[bidiUtil.R] = this.charWidths[bidiUtil.AN] = _bidiCharWidth; + this.charWidths[bidiUtil.R_H] = useragent.isChrome ? _bidiCharWidth : _bidiCharWidth * 0.45; + this.charWidths[bidiUtil.B] = 0; + + if (this.currentRow !== null) { + this.updateBidiMap(); + } + }; + + this.getShowInvisibles = function() { + return this.showInvisibles; + }; + + this.setShowInvisibles = function(showInvisibles) { + this.showInvisibles = showInvisibles; + if (this.currentRow !== null) { + this.updateBidiMap(); + } + }; + + this.setEolChar = function(eolChar) { + this.EOL = eolChar; + }; + + this.setTextDir = function(isRtlDir) { + this.isRtlDir = isRtlDir; + }; /** * Returns offset of character at position defined by column. @@ -208,26 +208,26 @@ var BidiHandler = function(session) { * * @return {int} horizontal pixel offset of given screen column **/ - this.getPosLeft = function(col) { - col -= this.wrapIndent; - var visualIdx = bidiUtil.getVisualFromLogicalIdx(col > 0 ? col - 1 : 0, this.bidiMap), - levels = this.bidiMap.bidiLevels, left = 0; + this.getPosLeft = function(col) { + col -= this.wrapIndent; + var visualIdx = bidiUtil.getVisualFromLogicalIdx(col > 0 ? col - 1 : 0, this.bidiMap), + levels = this.bidiMap.bidiLevels, left = 0; - if (col === 0 && levels[visualIdx] % 2 !== 0) - visualIdx++; + if (col === 0 && levels[visualIdx] % 2 !== 0) + visualIdx++; - for (var i = 0; i < visualIdx; i++) { - left += this.charWidths[levels[i]]; - } + for (var i = 0; i < visualIdx; i++) { + left += this.charWidths[levels[i]]; + } - if (col !== 0 && levels[visualIdx] % 2 === 0) - left += this.charWidths[levels[visualIdx]]; + if (col !== 0 && levels[visualIdx] % 2 === 0) + left += this.charWidths[levels[visualIdx]]; - if (this.wrapIndent) - left += this.wrapIndent * this.charWidths[bidiUtil.L]; + if (this.wrapIndent) + left += this.wrapIndent * this.charWidths[bidiUtil.L]; - return left; - }; + return left; + }; /** * Returns 'selections' - array of objects defining set of selection rectangles @@ -236,30 +236,30 @@ var BidiHandler = function(session) { * * @return {Array of Objects} Each object contains 'left' and 'width' values defining selection rectangle. **/ - this.getSelections = function(startCol, endCol) { - var map = this.bidiMap, levels = map.bidiLevels, level, offset = this.wrapIndent * this.charWidths[bidiUtil.L], selections = [], - selColMin = Math.min(startCol, endCol) - this.wrapIndent, selColMax = Math.max(startCol, endCol) - this.wrapIndent, - isSelected = false, isSelectedPrev = false, selectionStart = 0; - - for (var logIdx, visIdx = 0; visIdx < levels.length; visIdx++) { - logIdx = map.logicalFromVisual[visIdx]; - level = levels[visIdx]; - isSelected = (logIdx >= selColMin) && (logIdx < selColMax); - if (isSelected && !isSelectedPrev) { - selectionStart = offset; - } else if (!isSelected && isSelectedPrev) { - selections.push({left: selectionStart, width: offset - selectionStart}); - } - offset += this.charWidths[level]; - isSelectedPrev = isSelected; - } - - if (isSelected && (visIdx === levels.length)) { - selections.push({left: selectionStart, width: offset - selectionStart}); - } - - return selections; - }; + this.getSelections = function(startCol, endCol) { + var map = this.bidiMap, levels = map.bidiLevels, level, offset = this.wrapIndent * this.charWidths[bidiUtil.L], selections = [], + selColMin = Math.min(startCol, endCol) - this.wrapIndent, selColMax = Math.max(startCol, endCol) - this.wrapIndent, + isSelected = false, isSelectedPrev = false, selectionStart = 0; + + for (var logIdx, visIdx = 0; visIdx < levels.length; visIdx++) { + logIdx = map.logicalFromVisual[visIdx]; + level = levels[visIdx]; + isSelected = (logIdx >= selColMin) && (logIdx < selColMax); + if (isSelected && !isSelectedPrev) { + selectionStart = offset; + } else if (!isSelected && isSelectedPrev) { + selections.push({left: selectionStart, width: offset - selectionStart}); + } + offset += this.charWidths[level]; + isSelectedPrev = isSelected; + } + + if (isSelected && (visIdx === levels.length)) { + selections.push({left: selectionStart, width: offset - selectionStart}); + } + + return selections; + }; /** * Converts character coordinates on the screen to respective document column number @@ -267,52 +267,52 @@ var BidiHandler = function(session) { * * @return {Number} screen column number corresponding to given pixel offset **/ - this.offsetToCol = function(posX) { - var logicalIdx = 0, posX = Math.max(posX, 0), - offset = 0, visualIdx = 0, levels = this.bidiMap.bidiLevels, - charWidth = this.charWidths[levels[visualIdx]]; - - if (this.wrapIndent) { - posX -= this.wrapIndent * this.charWidths[bidiUtil.L]; - } - - while(posX > offset + charWidth/2) { - offset += charWidth; - if(visualIdx === levels.length - 1) { - /* quit when we on the right of the last character, flag this by charWidth = 0 */ - charWidth = 0; - break; - } - charWidth = this.charWidths[levels[++visualIdx]]; - } - - if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && (levels[visualIdx] % 2 === 0)){ - /* Bidi character on the left and None Bidi character on the right */ - if(posX < offset) - visualIdx--; - logicalIdx = this.bidiMap.logicalFromVisual[visualIdx]; - - } else if (visualIdx > 0 && (levels[visualIdx - 1] % 2 === 0) && (levels[visualIdx] % 2 !== 0)){ - /* None Bidi character on the left and Bidi character on the right */ - logicalIdx = 1 + ((posX > offset) ? this.bidiMap.logicalFromVisual[visualIdx] - : this.bidiMap.logicalFromVisual[visualIdx - 1]); - - } else if ((this.isRtlDir && visualIdx === levels.length - 1 && charWidth === 0 && (levels[visualIdx - 1] % 2 === 0)) - || (!this.isRtlDir && visualIdx === 0 && (levels[visualIdx] % 2 !== 0))){ - /* To the right of last character, which is None Bidi, in RTL direction or */ - /* to the left of first Bidi character, in LTR direction */ - logicalIdx = 1 + this.bidiMap.logicalFromVisual[visualIdx]; - } else { - /* Tweak visual position when Bidi character on the left in order to map it to corresponding logical position */ - if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && charWidth !== 0) - visualIdx--; - - /* Regular case */ - logicalIdx = this.bidiMap.logicalFromVisual[visualIdx]; - } - - return (logicalIdx + this.wrapIndent); - }; + this.offsetToCol = function(posX) { + var logicalIdx = 0, posX = Math.max(posX, 0), + offset = 0, visualIdx = 0, levels = this.bidiMap.bidiLevels, + charWidth = this.charWidths[levels[visualIdx]]; + + if (this.wrapIndent) { + posX -= this.wrapIndent * this.charWidths[bidiUtil.L]; + } + + while(posX > offset + charWidth/2) { + offset += charWidth; + if(visualIdx === levels.length - 1) { + /* quit when we on the right of the last character, flag this by charWidth = 0 */ + charWidth = 0; + break; + } + charWidth = this.charWidths[levels[++visualIdx]]; + } + + if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && (levels[visualIdx] % 2 === 0)){ + /* Bidi character on the left and None Bidi character on the right */ + if(posX < offset) + visualIdx--; + logicalIdx = this.bidiMap.logicalFromVisual[visualIdx]; + + } else if (visualIdx > 0 && (levels[visualIdx - 1] % 2 === 0) && (levels[visualIdx] % 2 !== 0)){ + /* None Bidi character on the left and Bidi character on the right */ + logicalIdx = 1 + ((posX > offset) ? this.bidiMap.logicalFromVisual[visualIdx] + : this.bidiMap.logicalFromVisual[visualIdx - 1]); + + } else if ((this.isRtlDir && visualIdx === levels.length - 1 && charWidth === 0 && (levels[visualIdx - 1] % 2 === 0)) + || (!this.isRtlDir && visualIdx === 0 && (levels[visualIdx] % 2 !== 0))){ + /* To the right of last character, which is None Bidi, in RTL direction or */ + /* to the left of first Bidi character, in LTR direction */ + logicalIdx = 1 + this.bidiMap.logicalFromVisual[visualIdx]; + } else { + /* Tweak visual position when Bidi character on the left in order to map it to corresponding logical position */ + if (visualIdx > 0 && (levels[visualIdx - 1] % 2 !== 0) && charWidth !== 0) + visualIdx--; + + /* Regular case */ + logicalIdx = this.bidiMap.logicalFromVisual[visualIdx]; + } + + return (logicalIdx + this.wrapIndent); + }; }).call(BidiHandler.prototype); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 928350895db..6aa68246847 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1176,7 +1176,6 @@ var EditSession = function(text, mode) { this.$undoSelect && !dontSelect && this.selection.setSelectionRange(lastUndoRange); - return lastUndoRange; }; diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index bddc4e7578e..14edaebb74a 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -170,9 +170,9 @@ var Cursor = function(parentEl) { if (!position) position = this.session.selection.getCursor(); var pos = this.session.documentToScreenPosition(position); - var cursorLeft = this.$padding + (this.session.$bidiHandler.isBidiRow(pos.row, position.row) ? - this.session.$bidiHandler.getPosLeft(pos.column) : - pos.column * this.config.characterWidth); + var cursorLeft = this.$padding + (this.session.$bidiHandler.isBidiRow(pos.row, position.row) + ? this.session.$bidiHandler.getPosLeft(pos.column) + : pos.column * this.config.characterWidth); var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) * this.config.lineHeight; diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 96ffddc1bcb..e6ccc90eb2a 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -78,10 +78,9 @@ var Marker = function(parentEl) { range = range.toScreenRange(this.session); if (marker.renderer) { var top = this.$getTop(range.start.row, config); - var left = this.$padding + (this.session.$bidiHandler.isBidiRow(range.start.row) ? - this.session.$bidiHandler.getPosLeft(range.start.column) : - range.start.column * config.characterWidth); - + var left = this.$padding + (this.session.$bidiHandler.isBidiRow(range.start.row) + ? this.session.$bidiHandler.getPosLeft(range.start.column) + : range.start.column * config.characterWidth); marker.renderer(html, range, left, top, config); } else if (marker.type == "fullLine") { this.drawFullLineMarker(html, range, marker.clazz, config); @@ -134,7 +133,7 @@ var Marker = function(parentEl) { if (this.session.$bidiHandler.isBidiRow(row)) { this.drawBidiSingleLineMarker(stringBuilder, lineRange, clazzModified, layerConfig, row == end ? 0 : 1, extraStyle); - } else { + } else { this.drawSingleLineMarker(stringBuilder, lineRange, clazzModified, layerConfig, row == end ? 0 : 1, extraStyle); } diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 7ed633e2424..5a83d4b1d4d 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -266,7 +266,6 @@ var Text = function(parentEl) { // 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); @@ -464,6 +463,7 @@ var Text = function(parentEl) { else var tokens = this.session.getTokens(row); + if (!onlyContents) { stringBuilder.push( "