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..e6b8e19c 100644
--- a/lib/ace/layer/cursor.js
+++ b/lib/ace/layer/cursor.js
@@ -170,11 +170,17 @@ 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, pos.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 +204,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..7238bedf 100644
--- a/lib/ace/layer/marker.js
+++ b/lib/ace/layer/marker.js
@@ -78,7 +78,7 @@ 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 + 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 +129,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 +145,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 + "";
}
};
@@ -367,6 +354,101 @@ 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; // TODO
+ while (len < column && i < tokens.length) {
+ var token = tokens[i++];
+
+ var self = this;
+ var replaceReg = /\t|&|<|( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])/g;
+ var replaceFunc = function(c, a, b, tabIdx, idx4) {
+ if (c.charCodeAt(0) == 32) {
+ return new Array(c.length+1).join(" ");
+ } else if (c == "\t") {
+ var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
+ screenColumn += tabSize - 1;
+ return self.$tabStrings[tabSize];
+ } else if (c == "&") {
+ if (useragent.isOldGecko)
+ return "&";
+ else
+ return "&";
+ } else if (c == "<") {
+ return "<";
+ } else if (c.match(/[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/)) {
+ if (self.showInvisibles) {
+ var space = new Array(c.length+1).join(self.SPACE_CHAR);
+ return "" + space + "";
+ } else {
+ return " ";
+ }
+ }
+ };
+
+ 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));
+
+ var output = value.replace(replaceReg, replaceFunc);
+
+
+ if (!this.$textToken[token.type]) {
+ var classes = "ace_" + token.type.replace(/\./g, " ace_");
+ stringBuilder.push("", output, "");
+ }
+ else {
+ stringBuilder.push(output);
+ }
+ }
+
+ // 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..199225ea 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,12 +1391,44 @@ 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);
+ 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 width = x + this.scroller.scrollLeft - canvasPos.left - this.$padding - dom.getPageScrollLeft();
+ var col = this.$findColumn(row, width);
var row = (y + this.scrollTop - canvasPos.top) / this.lineHeight;