diff --git a/demo/kitchen-sink/doclist.js b/demo/kitchen-sink/doclist.js index d003da8e..0b700d19 100644 --- a/demo/kitchen-sink/doclist.js +++ b/demo/kitchen-sink/doclist.js @@ -67,6 +67,7 @@ function makeHuge(txt) { var docs = { "docs/javascript.js": {order: 1, name: "JavaScript"}, + "docs/international.md": "International Text", "docs/latex.tex": {name: "LaTeX", wrapped: true}, "docs/markdown.md": {name: "Markdown", wrapped: true}, diff --git a/demo/kitchen-sink/docs/international.md b/demo/kitchen-sink/docs/international.md new file mode 100644 index 00000000..1170ab12 --- /dev/null +++ b/demo/kitchen-sink/docs/international.md @@ -0,0 +1,7 @@ +Pinyin Simplified +----------------- +反对方山东队但是上 + +Thai +---- +อักษรไทย diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 9689ea5c..fb066434 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1832,7 +1832,6 @@ var EditSession = function(text, mode) { // "Tokens" var CHAR = 1, - CHAR_EXT = 2, PLACEHOLDER_START = 3, PLACEHOLDER_BODY = 4, PUNCTUATION = 9, @@ -1862,10 +1861,6 @@ var EditSession = function(text, mode) { // Get all the TAB_SPACEs. replace(/12/g, function() { len -= 1; - }). - // Get all the CHAR_EXT/multipleWidth characters. - replace(/2/g, function() { - len -= 1; }); lastDocSplit += len; @@ -1959,7 +1954,7 @@ var EditSession = function(text, mode) { // === ELSE === split = lastSplit + wrapLimit; - // The split is inside of a CHAR or CHAR_EXT token and no space + // The split is inside of a CHAR token and no space // around -> force a split. addSplit(split); } @@ -1993,10 +1988,6 @@ var EditSession = function(text, mode) { arr.push(SPACE); } else if((c > 39 && c < 48) || (c > 57 && c < 64)) { arr.push(PUNCTUATION); - } - // full width characters - else if (c >= 0x1100 && isFullWidth(c)) { - arr.push(CHAR, CHAR_EXT); } else { arr.push(CHAR); } @@ -2028,12 +2019,7 @@ var EditSession = function(text, mode) { if (c == 9) { screenColumn += this.getScreenTabSize(screenColumn); } - // full width characters - else if (c >= 0x1100 && isFullWidth(c)) { - screenColumn += 2; - } else { screenColumn += 1; - } if (screenColumn > maxScreenColumn) { break; } diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 4578482a..2d4a720e 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -170,11 +170,13 @@ 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 textWidth = this.config.textWidth(pos.row, position.column); + var cursorLeft = this.$padding + textWidth; var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) * this.config.lineHeight; + var cursorWidth = (this.config.textWidth(pos.row, pos.column + 1) - textWidth) || this.config.characterWidth; - return {left : cursorLeft, top : cursorTop}; + return {left : cursorLeft, top : cursorTop, width: cursorWidth}; }; this.update = function(config) { @@ -198,7 +200,7 @@ var Cursor = function(parentEl) { style.left = pixelPos.left + "px"; style.top = pixelPos.top + "px"; - style.width = config.characterWidth + "px"; + style.width = pixelPos.width + "px"; style.height = config.lineHeight + "px"; } while (this.cursors.length > cursorIndex) diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index cd1b992d..af21fcd4 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -75,10 +75,13 @@ var Marker = function(parentEl) { var range = marker.range.clipRows(config.firstRow, config.lastRow); if (range.isEmpty()) continue; + var docRange = range; range = range.toScreenRange(this.session); + range.start.column = docRange.start.column; + range.end.column = docRange.end.column; if (marker.renderer) { var top = this.$getTop(range.start.row, config); - var left = this.$padding + range.start.column * config.characterWidth; + var left = this.$padding + config.textWidth(range.start.row, range.start.column); marker.renderer(html, range, left, top, config); } else if (marker.type == "fullLine") { this.drawFullLineMarker(html, range, marker.clazz, config); @@ -129,8 +132,10 @@ var Marker = function(parentEl) { // from selection start to the end of the line var padding = this.$padding; var height = config.lineHeight; + var textWidth = config.textWidth(range.start.row, range.start.column); + var left = padding + textWidth; + var width = config.width - textWidth; var top = this.$getTop(range.start.row, config); - var left = padding + range.start.column * config.characterWidth; extraStyle = extraStyle || ""; stringBuilder.push( @@ -143,7 +148,7 @@ var Marker = function(parentEl) { // from start of the last line to the selection end top = this.$getTop(range.end.row, config); - var width = range.end.column * config.characterWidth; + var width = config.textWidth(range.end.row, range.end.column); stringBuilder.push( "
" + space + ""; } else if (b) { return "" + self.SPACE_CHAR + ""; - } else { - screenColumn += 1; - return "" + c + ""; } }; @@ -359,7 +346,7 @@ var Text = function(parentEl) { var classes = "ace_" + token.type.replace(/\./g, " ace_"); var style = ""; if (token.type == "fold") - style = " style='width:" + (token.value.length * this.config.characterWidth) + "px;' "; + style = " style='width:" + (value.length * this.config.characterWidth) + "px;' "; stringBuilder.push("", output, ""); } else { @@ -367,6 +354,65 @@ var Text = function(parentEl) { } return screenColumn + value.length; }; + + this.$measureText = function(tokens, column) { + // build HTML for tokens + var stringBuilder = []; + var len = 0; + var i = 0; + var screenColumn = 0; + while (len < column && i < tokens.length) { + var token = tokens[i++]; + var value = token.value; + + // truncate once length is larger than 'column' + len += value.length; + if (len > column) + value = value.substring(0, value.length - (len - column)); + + screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value); + } + + // render line off screen + var el = document.createElement("div"); + var style = el.style; + style.position = "absolute"; + style.top = "-1000px"; + el.className = "ace_line"; + el.innerHTML = stringBuilder.join(""); + this.element.appendChild(el); + + // measure pixel length + var width = el.offsetWidth; + this.element.removeChild(el); + + return width; + } + + this.textWidth = function(row, column) { + //return this.$characterSize.width * column; + var line = this.session.getTokens(row); + + // cache in tokens object + // this way the cache gets invalidated automatically when the tokens change + if (line.widthCache && line.widthCache[column]) { + // invalidate if font size has changed + if (line.widthCache.rowHeight == this.getLineHeight()) { + return line.widthCache[column]; + } + else + delete line.widthCache; + } + + var width = this.$measureText(line, column); + + if (!line.widthCache) + line.widthCache = { rowHeight: this.getLineHeight() }; + + line.widthCache[column] = width; + + return width; + }; this.renderIndentGuide = function(stringBuilder, value, max) { var cols = value.search(this.$indentGuideRe); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 33f3a906..1e4746e1 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -150,6 +150,7 @@ var VirtualRenderer = function(container, theme) { lastRow : 0, lineHeight : 0, characterWidth : 0, + textWidth: function() { return 1; }, minHeight : 1, maxHeight : 1, offset : 0, @@ -997,6 +998,7 @@ var VirtualRenderer = function(container, theme) { lastRow : lastRow, lineHeight : lineHeight, characterWidth : this.characterWidth, + textWidth: this.$textLayer.textWidth.bind(this.$textLayer), minHeight : minHeight, maxHeight : maxHeight, offset : offset, @@ -1389,16 +1391,51 @@ var VirtualRenderer = function(container, theme) { return {row: row, column: col, side: offset - col > 0 ? 1 : -1}; }; + this.$findColumn = function(row, width) { + // binary search to find the screen column + var min = 0; + var max = this.session.getLine(row).length; + + while (true) { + if (max <= 0) + return null; + + // if the range has length one pick the closes characte + if (max-min == 1) { + var wMin = this.$textLayer.textWidth(row, min); + var wMax = this.$textLayer.textWidth(row, max); + + if (Math.abs(wMin-width) < Math.abs(wMax-width)) + return min; + else + return max; + } + else { + // same as Math.floor((max-min)/2) but faster + var pivot = min + ((max - min) >> 1); + var w = this.$textLayer.textWidth(row, pivot, true); + if (w == width) + return pivot; + else if (w > width) + max = pivot; + else + min = pivot; + } + } + }; + this.screenToTextCoordinates = function(x, y) { var canvasPos = this.scroller.getBoundingClientRect(); - - var col = Math.round( - (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth - ); - var row = (y + this.scrollTop - canvasPos.top) / this.lineHeight; - return this.session.screenToDocumentPosition(row, Math.max(col, 0)); + var width = x + this.scroller.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft(); + + + var pos = this.session.screenToDocumentPosition(row, Math.max(col, 0)); + + var col = this.$findColumn(pos.row, width); + pos.column = col + return pos }; /**