diff --git a/doc/manual.html b/doc/manual.html
index fd6729770030c8c53d836a3b34226ff9126c91e0..3f591492b6d84c41b23ca01e746f6371cfd4aec7 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -387,9 +387,21 @@
       <dd>When highlighting long lines, in order to stay responsive,
       the editor will give up and simply style the rest of the line as
       plain text when it reaches a certain position. The default is
-      10000. You can set this to <code>Infinity</code> to turn off
+      10 000. You can set this to <code>Infinity</code> to turn off
       this behavior.</dd>
 
+      <dt id="option_crudeMeasuringFrom"><code><strong>crudeMeasuringFrom</strong>: number</code></dt>
+      <dd>When measuring the character positions in long lines, any
+      line longer than this number (default is 10 000),
+      when <a href="#option_lineWrapping">line wrapping</a>
+      is <strong>off</strong>, will simply be assumed to consist of
+      same-sized characters. This means that, on the one hand,
+      measuring will be inaccurate when characters of varying size,
+      right-to-left text, markers, or other irregular elements are
+      present. On the other hand, it means that having such a line
+      won't freeze the user interface because of the expensiveness of
+      the measurements.</dd>
+
       <dt id="option_viewportMargin"><code><strong>viewportMargin</strong>: integer</code></dt>
       <dd>Specifies the amount of lines that are rendered above and
       below the part of the document that's currently scrolled into
diff --git a/lib/codemirror.js b/lib/codemirror.js
index eb393c29b2c3d6869cabfa850a5c652f9ed1e667..f7e4a0e69b1029bf71a6dd0da678a7b532eb4cd7 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -972,6 +972,10 @@ window.CodeMirror = (function() {
   function measureChar(cm, line, ch, data, bias) {
     var dir = -1;
     data = data || measureLine(cm, line);
+    if (data.crude) {
+      var left = data.left + ch * data.width;
+      return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
+    }
 
     for (var pos = ch;; pos += dir) {
       var r = data[pos];
@@ -1020,6 +1024,9 @@ window.CodeMirror = (function() {
   }
 
   function measureLineInner(cm, line) {
+    if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
+      return crudelyMeasureLine(cm, line);
+
     var display = cm.display, measure = emptyArray(line.text.length);
     var pre = lineContent(cm, line, measure, true);
 
@@ -1106,6 +1113,15 @@ window.CodeMirror = (function() {
     return data;
   }
 
+  function crudelyMeasureLine(cm, line) {
+    var copy = new Line(line.text.slice(0, 100), null);
+    if (line.textClass) copy.textClass = line.textClass;
+    var measure = measureLineInner(cm, copy);
+    var left = measureChar(cm, copy, 0, measure, "left");
+    var right = measureChar(cm, copy, 99, measure, "right");
+    return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
+  }
+
   function measureLineWidth(cm, line) {
     var hasBadSpan = false;
     if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
@@ -1113,7 +1129,8 @@ window.CodeMirror = (function() {
       if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
     }
     var cached = !hasBadSpan && findCachedMeasurement(cm, line);
-    if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right;
+    if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
+      return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
 
     var pre = lineContent(cm, line, null, true);
     var end = pre.appendChild(zeroWidthElement(cm.display.measure));
@@ -3273,6 +3290,7 @@ window.CodeMirror = (function() {
   option("historyEventDelay", 500);
   option("viewportMargin", 10, function(cm){cm.refresh();}, true);
   option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
+  option("crudeMeasuringFrom", 10000);
   option("moveInputWithCursor", true, function(cm, val) {
     if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
   });