Skip to content

Commit

Permalink
Always use DOM to find string widths
Browse files Browse the repository at this point in the history
CodeMirror no longer has a concept of 'character width', since that
was just too broken -- tabs and non-ascii characters can have non-
standard widths, even in monospace fonts. We now build up a DOM
structure and measure its width whenever we have to determine the
width of something.
  • Loading branch information
marijnh committed Apr 14, 2011
1 parent 468928f commit 51b7f7e
Showing 1 changed file with 49 additions and 38 deletions.
87 changes: 49 additions & 38 deletions lib/codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ var CodeMirror = (function() {
// This mess creates the base DOM structure for the editor.
wrapper.innerHTML =
'<div style="position: relative">' + // Set to the height of the text, causes scrolling
'<pre style="position: relative; height: 0; visibility: hidden; overflow: hidden;">' + // To measure line/char size
'<span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></pre>' +
'<div style="position: absolute; height: 0; width: 0; overflow: hidden;"></div>' +
'<div style="position: relative">' + // Moved around its parent to cover visible view
'<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
'<div style="overflow: hidden; position: absolute; width: 0; left: 0">' + // Wraps and hides input textarea
Expand Down Expand Up @@ -707,11 +706,7 @@ var CodeMirror = (function() {
updateGutter();
}

var old = measure.firstChild.firstChild.nodeValue;
try {
measure.firstChild.firstChild.nodeValue = maxLine;
var textWidth = measure.firstChild.offsetWidth;
} finally {measure.firstChild.firstChild.nodeValue = old;}
var textWidth = stringWidth(maxLine);
lineSpace.style.width = textWidth > wrapper.clientWidth ? textWidth + "px" : "";

// Since this is all rather error prone, it is honoured with the
Expand Down Expand Up @@ -1032,35 +1027,44 @@ var CodeMirror = (function() {
return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
}

function stringWidth(str) {
measure.innerHTML = "<pre><span>x</span></pre>";
measure.firstChild.firstChild.firstChild.nodeValue = str;
return measure.firstChild.firstChild.offsetWidth || 10;
}
// These are used to go from pixel positions to character
// positions, taking tabs into account.
// positions, taking varying character widths into account.
function charX(line, pos) {
var text = lines[line].text, span = measure.firstChild;
if (text.lastIndexOf("\t", pos) == -1) return pos * charWidth();
var old = span.firstChild.nodeValue;
try {
span.firstChild.nodeValue = text.slice(0, pos);
return span.offsetWidth;
} finally {span.firstChild.nodeValue = old;}
if (pos == 0) return 0;
measure.innerHTML = "<pre><span>" + lines[line].getHTML(null, null, false, pos) + "</span></pre>";
return measure.firstChild.firstChild.offsetWidth;
}
function charFromX(line, x) {
var text = lines[line].text, cw = charWidth();
if (x <= 0) return 0;
if (text.indexOf("\t") == -1) return Math.min(text.length, Math.round(x / cw));
var mspan = measure.firstChild, mtext = mspan.firstChild, old = mtext.nodeValue;
try {
mtext.nodeValue = text;
var from = 0, fromX = 0, to = text.length, toX = mspan.offsetWidth;
if (x > toX) return to;
for (;;) {
if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
var middle = Math.ceil((from + to) / 2);
mtext.nodeValue = text.slice(0, middle);
var curX = mspan.offsetWidth;
if (curX > x) {to = middle; toX = curX;}
else {from = middle; fromX = curX;}
}
} finally {mtext.nodeValue = old;}
var lineObj = lines[line], text = lineObj.text;
function getX(len) {
measure.innerHTML = "<pre><span>" + lineObj.getHTML(null, null, false, len) + "</span></pre>";
return measure.firstChild.firstChild.offsetWidth;
}
var from = 0, fromX = 0, to = text.length, toX;
// Guess a suitable upper bound for our search.
var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
for (;;) {
var estX = getX(estimated);
if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
else {toX = estX; to = estimated; break;}
}
if (x > toX) return to;
// Try to guess a suitable lower bound as well.
estimated = Math.floor(to * 0.8); estX = getX(estimated);
if (estX < x) {from = estimated; fromX = estX;}
// Do a binary search between these bounds.
for (;;) {
if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
if (middleX > x) {to = middle; toX = middleX;}
else {from = middle; fromX = middleX;}
}
}

function localCoords(pos, inLineWrap) {
Expand All @@ -1075,9 +1079,9 @@ var CodeMirror = (function() {
function lineHeight() {
var nlines = lineDiv.childNodes.length;
if (nlines) return lineDiv.offsetHeight / nlines;
else return measure.firstChild.offsetHeight || 1;
measure.innerHTML = "<pre>x</pre>";
return measure.firstChild.offsetHeight || 1;
}
function charWidth() {return (measure.firstChild.offsetWidth || 320) / 40;}
function paddingTop() {return lineSpace.offsetTop;}
function paddingLeft() {return lineSpace.offsetLeft;}

Expand Down Expand Up @@ -1643,7 +1647,7 @@ var CodeMirror = (function() {
indentation: function() {return countColumn(this.text);},
// Produces an HTML fragment for the line, taking selection,
// marking, and highlighting into account.
getHTML: function(sfrom, sto, includePre) {
getHTML: function(sfrom, sto, includePre, endAt) {
var html = [];
if (includePre)
html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
Expand All @@ -1654,11 +1658,18 @@ var CodeMirror = (function() {
}
var st = this.styles, allText = this.text, marked = this.marked;
if (sfrom == sto) sfrom = null;
var len = allText.length;
if (endAt != null) len = Math.min(endAt, len);

if (!allText)
if (!allText && endAt == null)
span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
else if (!marked && sfrom == null)
for (var i = 0, e = st.length; i < e; i+=2) span(st[i], st[i+1]);
for (var i = 0, ch = 0; ch < len; i+=2) {
var str = st[i], l = str.length;
if (ch + l > len) str = str.slice(0, len - ch);
ch += l;
span(str, st[i+1]);
}
else {
var pos = 0, i = 0, text = "", style, sg = 0;
var markpos = -1, mark = null;
Expand All @@ -1669,8 +1680,8 @@ var CodeMirror = (function() {
}
}
nextMark();
while (pos < allText.length) {
var upto = allText.length;
while (pos < len) {
var upto = len;
var extraStyle = "";
if (sfrom != null) {
if (sfrom > pos) upto = sfrom;
Expand Down

0 comments on commit 51b7f7e

Please sign in to comment.