Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2fbb3ed5 | ||
|
|
bcba9bea6e |
7 changed files with 129 additions and 44 deletions
|
|
@ -67,6 +67,7 @@ function makeHuge(txt) {
|
||||||
|
|
||||||
var docs = {
|
var docs = {
|
||||||
"docs/javascript.js": {order: 1, name: "JavaScript"},
|
"docs/javascript.js": {order: 1, name: "JavaScript"},
|
||||||
|
"docs/international.md": "International Text",
|
||||||
|
|
||||||
"docs/latex.tex": {name: "LaTeX", wrapped: true},
|
"docs/latex.tex": {name: "LaTeX", wrapped: true},
|
||||||
"docs/markdown.md": {name: "Markdown", wrapped: true},
|
"docs/markdown.md": {name: "Markdown", wrapped: true},
|
||||||
|
|
|
||||||
7
demo/kitchen-sink/docs/international.md
Normal file
7
demo/kitchen-sink/docs/international.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Pinyin Simplified
|
||||||
|
-----------------
|
||||||
|
反对方山东队但是上
|
||||||
|
|
||||||
|
Thai
|
||||||
|
----
|
||||||
|
อักษรไทย
|
||||||
|
|
@ -1832,7 +1832,6 @@ var EditSession = function(text, mode) {
|
||||||
|
|
||||||
// "Tokens"
|
// "Tokens"
|
||||||
var CHAR = 1,
|
var CHAR = 1,
|
||||||
CHAR_EXT = 2,
|
|
||||||
PLACEHOLDER_START = 3,
|
PLACEHOLDER_START = 3,
|
||||||
PLACEHOLDER_BODY = 4,
|
PLACEHOLDER_BODY = 4,
|
||||||
PUNCTUATION = 9,
|
PUNCTUATION = 9,
|
||||||
|
|
@ -1862,10 +1861,6 @@ var EditSession = function(text, mode) {
|
||||||
// Get all the TAB_SPACEs.
|
// Get all the TAB_SPACEs.
|
||||||
replace(/12/g, function() {
|
replace(/12/g, function() {
|
||||||
len -= 1;
|
len -= 1;
|
||||||
}).
|
|
||||||
// Get all the CHAR_EXT/multipleWidth characters.
|
|
||||||
replace(/2/g, function() {
|
|
||||||
len -= 1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
lastDocSplit += len;
|
lastDocSplit += len;
|
||||||
|
|
@ -1959,7 +1954,7 @@ var EditSession = function(text, mode) {
|
||||||
|
|
||||||
// === ELSE ===
|
// === ELSE ===
|
||||||
split = lastSplit + wrapLimit;
|
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.
|
// around -> force a split.
|
||||||
addSplit(split);
|
addSplit(split);
|
||||||
}
|
}
|
||||||
|
|
@ -1993,10 +1988,6 @@ var EditSession = function(text, mode) {
|
||||||
arr.push(SPACE);
|
arr.push(SPACE);
|
||||||
} else if((c > 39 && c < 48) || (c > 57 && c < 64)) {
|
} else if((c > 39 && c < 48) || (c > 57 && c < 64)) {
|
||||||
arr.push(PUNCTUATION);
|
arr.push(PUNCTUATION);
|
||||||
}
|
|
||||||
// full width characters
|
|
||||||
else if (c >= 0x1100 && isFullWidth(c)) {
|
|
||||||
arr.push(CHAR, CHAR_EXT);
|
|
||||||
} else {
|
} else {
|
||||||
arr.push(CHAR);
|
arr.push(CHAR);
|
||||||
}
|
}
|
||||||
|
|
@ -2028,12 +2019,7 @@ var EditSession = function(text, mode) {
|
||||||
if (c == 9) {
|
if (c == 9) {
|
||||||
screenColumn += this.getScreenTabSize(screenColumn);
|
screenColumn += this.getScreenTabSize(screenColumn);
|
||||||
}
|
}
|
||||||
// full width characters
|
|
||||||
else if (c >= 0x1100 && isFullWidth(c)) {
|
|
||||||
screenColumn += 2;
|
|
||||||
} else {
|
|
||||||
screenColumn += 1;
|
screenColumn += 1;
|
||||||
}
|
|
||||||
if (screenColumn > maxScreenColumn) {
|
if (screenColumn > maxScreenColumn) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,11 +170,13 @@ var Cursor = function(parentEl) {
|
||||||
if (!position)
|
if (!position)
|
||||||
position = this.session.selection.getCursor();
|
position = this.session.selection.getCursor();
|
||||||
var pos = this.session.documentToScreenPosition(position);
|
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)) *
|
var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) *
|
||||||
this.config.lineHeight;
|
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) {
|
this.update = function(config) {
|
||||||
|
|
@ -198,7 +200,7 @@ var Cursor = function(parentEl) {
|
||||||
|
|
||||||
style.left = pixelPos.left + "px";
|
style.left = pixelPos.left + "px";
|
||||||
style.top = pixelPos.top + "px";
|
style.top = pixelPos.top + "px";
|
||||||
style.width = config.characterWidth + "px";
|
style.width = pixelPos.width + "px";
|
||||||
style.height = config.lineHeight + "px";
|
style.height = config.lineHeight + "px";
|
||||||
}
|
}
|
||||||
while (this.cursors.length > cursorIndex)
|
while (this.cursors.length > cursorIndex)
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,13 @@ var Marker = function(parentEl) {
|
||||||
var range = marker.range.clipRows(config.firstRow, config.lastRow);
|
var range = marker.range.clipRows(config.firstRow, config.lastRow);
|
||||||
if (range.isEmpty()) continue;
|
if (range.isEmpty()) continue;
|
||||||
|
|
||||||
|
var docRange = range;
|
||||||
range = range.toScreenRange(this.session);
|
range = range.toScreenRange(this.session);
|
||||||
|
range.start.column = docRange.start.column;
|
||||||
|
range.end.column = docRange.end.column;
|
||||||
if (marker.renderer) {
|
if (marker.renderer) {
|
||||||
var top = this.$getTop(range.start.row, config);
|
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);
|
marker.renderer(html, range, left, top, config);
|
||||||
} else if (marker.type == "fullLine") {
|
} else if (marker.type == "fullLine") {
|
||||||
this.drawFullLineMarker(html, range, marker.clazz, config);
|
this.drawFullLineMarker(html, range, marker.clazz, config);
|
||||||
|
|
@ -129,8 +132,10 @@ var Marker = function(parentEl) {
|
||||||
// from selection start to the end of the line
|
// from selection start to the end of the line
|
||||||
var padding = this.$padding;
|
var padding = this.$padding;
|
||||||
var height = config.lineHeight;
|
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 top = this.$getTop(range.start.row, config);
|
||||||
var left = padding + range.start.column * config.characterWidth;
|
|
||||||
extraStyle = extraStyle || "";
|
extraStyle = extraStyle || "";
|
||||||
|
|
||||||
stringBuilder.push(
|
stringBuilder.push(
|
||||||
|
|
@ -143,7 +148,7 @@ var Marker = function(parentEl) {
|
||||||
|
|
||||||
// from start of the last line to the selection end
|
// from start of the last line to the selection end
|
||||||
top = this.$getTop(range.end.row, config);
|
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(
|
stringBuilder.push(
|
||||||
"<div class='", clazz, "' style='",
|
"<div class='", clazz, "' style='",
|
||||||
|
|
@ -171,10 +176,11 @@ var Marker = function(parentEl) {
|
||||||
// Draws a marker which covers part or whole width of a single screen line
|
// Draws a marker which covers part or whole width of a single screen line
|
||||||
this.drawSingleLineMarker = function(stringBuilder, range, clazz, config, extraLength, extraStyle) {
|
this.drawSingleLineMarker = function(stringBuilder, range, clazz, config, extraLength, extraStyle) {
|
||||||
var height = config.lineHeight;
|
var height = config.lineHeight;
|
||||||
var width = (range.end.column + (extraLength || 0) - range.start.column) * config.characterWidth;
|
var textWidth = config.textWidth(range.start.row, range.start.column);
|
||||||
|
var width = config.textWidth(range.start.row, range.end.column + (extraLength || 0)) - textWidth;
|
||||||
|
|
||||||
var top = this.$getTop(range.start.row, config);
|
var top = this.$getTop(range.start.row, config);
|
||||||
var left = this.$padding + range.start.column * config.characterWidth;
|
var left = this.$padding + textWidth;
|
||||||
|
|
||||||
stringBuilder.push(
|
stringBuilder.push(
|
||||||
"<div class='", clazz, "' style='",
|
"<div class='", clazz, "' style='",
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ var Text = function(parentEl) {
|
||||||
|
|
||||||
this.$renderToken = function(stringBuilder, screenColumn, token, value) {
|
this.$renderToken = function(stringBuilder, screenColumn, token, value) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var replaceReg = /\t|&|<|( +)|([\x00-\x1f\x80-\xa0\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\u3000\uFEFF])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]/g;
|
var replaceReg = /\t|&|<|( +)|([\x00-\x1f\x80-\xa0\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\uFEFF])]/g;
|
||||||
var replaceFunc = function(c, a, b, tabIdx, idx4) {
|
var replaceFunc = function(c, a, b, tabIdx, idx4) {
|
||||||
if (a) {
|
if (a) {
|
||||||
return self.showInvisibles ?
|
return self.showInvisibles ?
|
||||||
|
|
@ -335,21 +335,8 @@ var Text = function(parentEl) {
|
||||||
var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
|
var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
|
||||||
screenColumn += tabSize - 1;
|
screenColumn += tabSize - 1;
|
||||||
return self.$tabStrings[tabSize];
|
return self.$tabStrings[tabSize];
|
||||||
} else if (c == "\u3000") {
|
|
||||||
// U+3000 is both invisible AND full-width, so must be handled uniquely
|
|
||||||
var classToUse = self.showInvisibles ? "ace_cjk ace_invisible ace_invisible_space" : "ace_cjk";
|
|
||||||
var space = self.showInvisibles ? self.SPACE_CHAR : "";
|
|
||||||
screenColumn += 1;
|
|
||||||
return "<span class='" + classToUse + "' style='width:" +
|
|
||||||
(self.config.characterWidth * 2) +
|
|
||||||
"px'>" + space + "</span>";
|
|
||||||
} else if (b) {
|
} else if (b) {
|
||||||
return "<span class='ace_invisible ace_invisible_space ace_invalid'>" + self.SPACE_CHAR + "</span>";
|
return "<span class='ace_invisible ace_invisible_space ace_invalid'>" + self.SPACE_CHAR + "</span>";
|
||||||
} else {
|
|
||||||
screenColumn += 1;
|
|
||||||
return "<span class='ace_cjk' style='width:" +
|
|
||||||
(self.config.characterWidth * 2) +
|
|
||||||
"px'>" + c + "</span>";
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -359,7 +346,7 @@ var Text = function(parentEl) {
|
||||||
var classes = "ace_" + token.type.replace(/\./g, " ace_");
|
var classes = "ace_" + token.type.replace(/\./g, " ace_");
|
||||||
var style = "";
|
var style = "";
|
||||||
if (token.type == "fold")
|
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("<span class='", classes, "'", style, ">", output, "</span>");
|
stringBuilder.push("<span class='", classes, "'", style, ">", output, "</span>");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -367,6 +354,65 @@ var Text = function(parentEl) {
|
||||||
}
|
}
|
||||||
return screenColumn + value.length;
|
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) {
|
this.renderIndentGuide = function(stringBuilder, value, max) {
|
||||||
var cols = value.search(this.$indentGuideRe);
|
var cols = value.search(this.$indentGuideRe);
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ var VirtualRenderer = function(container, theme) {
|
||||||
lastRow : 0,
|
lastRow : 0,
|
||||||
lineHeight : 0,
|
lineHeight : 0,
|
||||||
characterWidth : 0,
|
characterWidth : 0,
|
||||||
|
textWidth: function() { return 1; },
|
||||||
minHeight : 1,
|
minHeight : 1,
|
||||||
maxHeight : 1,
|
maxHeight : 1,
|
||||||
offset : 0,
|
offset : 0,
|
||||||
|
|
@ -997,6 +998,7 @@ var VirtualRenderer = function(container, theme) {
|
||||||
lastRow : lastRow,
|
lastRow : lastRow,
|
||||||
lineHeight : lineHeight,
|
lineHeight : lineHeight,
|
||||||
characterWidth : this.characterWidth,
|
characterWidth : this.characterWidth,
|
||||||
|
textWidth: this.$textLayer.textWidth.bind(this.$textLayer),
|
||||||
minHeight : minHeight,
|
minHeight : minHeight,
|
||||||
maxHeight : maxHeight,
|
maxHeight : maxHeight,
|
||||||
offset : offset,
|
offset : offset,
|
||||||
|
|
@ -1389,16 +1391,51 @@ var VirtualRenderer = function(container, theme) {
|
||||||
return {row: row, column: col, side: offset - col > 0 ? 1 : -1};
|
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) {
|
this.screenToTextCoordinates = function(x, y) {
|
||||||
var canvasPos = this.scroller.getBoundingClientRect();
|
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;
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue