Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/phpmyadmin/phpmyadmin.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Čihař <mcihar@suse.cz>2012-01-27 14:00:49 +0400
committerMichal Čihař <mcihar@suse.cz>2012-01-27 14:00:51 +0400
commit9b9d3e5cbc59def734181a4bd40a48205651d83b (patch)
tree73b359d8488dc1fcdf15a07c06752ec139f0edf2 /js/codemirror
parent8e30c72834a64dfa9f984466abe0071208dc6bd9 (diff)
Update CodeMirror to 2.18
This brings lot of fixes and introduces line wrapping. Upgrading to latest 2.1x should be safe, in future we will want to move to 2.2.
Diffstat (limited to 'js/codemirror')
-rw-r--r--js/codemirror/lib/codemirror.js1525
1 files changed, 1083 insertions, 442 deletions
diff --git a/js/codemirror/lib/codemirror.js b/js/codemirror/lib/codemirror.js
index 27bec72ff1..8e9a34edc6 100644
--- a/js/codemirror/lib/codemirror.js
+++ b/js/codemirror/lib/codemirror.js
@@ -1,3 +1,5 @@
+// CodeMirror v2.18
+
// All functions that need access to the editor's state live inside
// the CodeMirror function. Below that, at the bottom of the file,
// some utilities are defined.
@@ -16,18 +18,19 @@ var CodeMirror = (function() {
var targetDocument = options["document"];
// The element in which the editor lives.
var wrapper = targetDocument.createElement("div");
- wrapper.className = "CodeMirror";
+ wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
// This mess creates the base DOM structure for the editor.
wrapper.innerHTML =
'<div style="overflow: hidden; position: relative; width: 1px; height: 0px;">' + // Wraps and hides input textarea
- '<textarea style="position: absolute; width: 2px;" wrap="off"></textarea></div>' +
+ '<textarea style="position: absolute; width: 10000px;" wrap="off" ' +
+ 'autocorrect="off" autocapitalize="off"></textarea></div>' +
'<div class="CodeMirror-scroll cm-s-' + options.theme + '">' +
'<div style="position: relative">' + // Set to the height of the text, causes scrolling
- '<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>' +
// Provides positioning relative to (visible) text origin
'<div class="CodeMirror-lines"><div style="position: relative">' +
+ '<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden"></div>' +
'<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
'<div></div>' + // This DIV contains the actual code
'</div></div></div></div></div>';
@@ -35,21 +38,29 @@ var CodeMirror = (function() {
// I've never seen more elegant code in my life.
var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
scroller = wrapper.lastChild, code = scroller.firstChild,
- measure = code.firstChild, mover = measure.nextSibling,
- gutter = mover.firstChild, gutterText = gutter.firstChild,
- lineSpace = gutter.nextSibling.firstChild,
- cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
+ mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
+ lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
+ cursor = measure.nextSibling, lineDiv = cursor.nextSibling;
+ if (!webkit) lineSpace.draggable = true;
if (options.tabindex != null) input.tabindex = options.tabindex;
if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
+ // Check for problem with IE innerHTML not working when we have a
+ // P (or similar) parent node.
+ try { stringWidth("x"); }
+ catch (e) {
+ if (e.message.match(/unknown runtime/i))
+ e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
+ throw e;
+ }
+
// Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
var poll = new Delayed(), highlight = new Delayed(), blinker;
- // mode holds a mode API object. lines an array of Line objects
- // (see Line constructor), work an array of lines that should be
- // parsed, and history the undo history (instance of History
- // constructor).
- var mode, lines = [new Line("")], work, history = new History(), focused;
+ // mode holds a mode API object. doc is the tree of Line objects,
+ // work an array of lines that should be parsed, and history the
+ // undo history (instance of History constructor).
+ var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
loadMode();
// The selection. These are always maintained to point at valid
// positions. Inverted is used to remember that the user is
@@ -59,12 +70,12 @@ var CodeMirror = (function() {
// whether the user is holding shift. reducedSelection is a hack
// to get around the fact that we can't create inverted
// selections. See below.
- var shiftSelecting, reducedSelection, lastDoubleClick;
+ var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText;
// Variables used by startOperation/endOperation to track what
// happened during the operation.
- var updateInput, changes, textChanged, selectionChanged, leaveInputAlone;
+ var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty;
// Current visible range (may be bigger than the view window).
- var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
+ var displayOffset = 0, showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null;
// editing will hold an object describing the things we put in the
// textarea, to help figure out whether something changed.
// bracketHighlighted is used to remember that a backet has been
@@ -76,17 +87,33 @@ var CodeMirror = (function() {
// Initialize the content.
operation(function(){setValue(options.value || ""); updateInput = false;})();
+ var history = new History();
+
+ var slowPollInterval = 2000;
+ // Gecko and Opera Linux do not reliably fire any event when starting an IME compose
+ var alwaysPollForIME = (!win && !mac) && (gecko || window.opera);
+ if (options.pollForIME && alwaysPollForIME) slowPollInterval = 50;
+ function keyMightStartIME(keyCode) {
+ return (win && ((gecko && keyCode == 229) || (window.opera && keyCode == 197))) || (mac && gecko);
+ }
// Register our event handlers.
connect(scroller, "mousedown", operation(onMouseDown));
+ connect(scroller, "dblclick", operation(onDoubleClick));
+ connect(lineSpace, "dragstart", onDragStart);
+ connect(lineSpace, "selectstart", e_preventDefault);
// Gecko browsers fire contextmenu *after* opening the menu, at
// which point we can't mess with it anymore. Context menu is
// handled in onMouseDown for Gecko.
if (!gecko) connect(scroller, "contextmenu", onContextMenu);
- connect(code, "dblclick", operation(onDblClick));
- connect(scroller, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
+ connect(scroller, "scroll", function() {
+ updateDisplay([]);
+ if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
+ if (options.onScroll) options.onScroll(instance);
+ });
connect(window, "resize", function() {updateDisplay(true);});
connect(input, "keyup", operation(onKeyUp));
+ connect(input, "input", function() {fastPoll(curKeyId);});
connect(input, "keydown", operation(onKeyDown));
connect(input, "keypress", operation(onKeyPress));
connect(input, "focus", onFocus);
@@ -98,44 +125,51 @@ var CodeMirror = (function() {
connect(scroller, "paste", function(){focusInput(); fastPoll();});
connect(input, "paste", function(){fastPoll();});
connect(input, "cut", function(){fastPoll();});
-
- // IE throws unspecified error in certain cases, when
+
+ // IE throws unspecified error in certain cases, when
// trying to access activeElement before onload
var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }
if (hasFocus) setTimeout(onFocus, 20);
else onBlur();
- function isLine(l) {return l >= 0 && l < lines.length;}
+ function isLine(l) {return l >= 0 && l < doc.size;}
// The instance object that we'll return. Mostly calls out to
// local functions in the CodeMirror function. Some do some extra
// range checking and/or clipping. operation is used to wrap the
// call so that changes it makes are tracked, and the display is
// updated afterwards.
- var instance = {
+ var instance = wrapper.CodeMirror = {
getValue: getValue,
setValue: operation(setValue),
getSelection: getSelection,
replaceSelection: operation(replaceSelection),
focus: function(){focusInput(); onFocus(); fastPoll();},
setOption: function(option, value) {
+ var oldVal = options[option];
options[option] = value;
- if (option == "lineNumbers" || option == "gutter") gutterChanged();
- else if (option == "mode" || option == "indentUnit") loadMode();
+ if (option == "mode" || option == "indentUnit") loadMode();
else if (option == "readOnly" && value == "nocursor") input.blur();
else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value);
+ else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
+ else if (option == "pollForIME" && alwaysPollForIME) slowPollInterval = value ? 50 : 2000;
+ if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme")
+ operation(gutterChanged)();
},
getOption: function(option) {return options[option];},
undo: operation(undo),
redo: operation(redo),
- indentLine: operation(function(n) {if (isLine(n)) indentLine(n, "smart");}),
+ indentLine: operation(function(n, dir) {
+ if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract");
+ }),
historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
+ clearHistory: function() {history = new History();},
matchBrackets: operation(function(){matchBrackets(true);}),
- getTokenAt: function(pos) {
+ getTokenAt: operation(function(pos) {
pos = clipPos(pos);
- return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
- },
+ return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
+ }),
getStateAfter: function(line) {
- line = clipLine(line == null ? lines.length - 1: line);
+ line = clipLine(line == null ? doc.size - 1: line);
return getStateBefore(line + 1);
},
cursorCoords: function(start){
@@ -145,24 +179,25 @@ var CodeMirror = (function() {
charCoords: function(pos){return pageCoords(clipPos(pos));},
coordsChar: function(coords) {
var off = eltOffset(lineSpace);
- var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight())));
- return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)});
+ return coordsChar(coords.x - off.left, coords.y - off.top);
},
getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);},
- markText: operation(function(a, b, c){return operation(markText(a, b, c));}),
- setMarker: addGutterMarker,
- clearMarker: removeGutterMarker,
+ markText: operation(markText),
+ setBookmark: setBookmark,
+ setMarker: operation(addGutterMarker),
+ clearMarker: operation(removeGutterMarker),
setLineClass: operation(setLineClass),
+ hideLine: operation(function(h) {return setLineHidden(h, true);}),
+ showLine: operation(function(h) {return setLineHidden(h, false);}),
lineInfo: lineInfo,
- addWidget: function(pos, node, scroll, where) {
+ addWidget: function(pos, node, scroll, vert, horiz) {
pos = localCoords(clipPos(pos));
var top = pos.yBot, left = pos.x;
node.style.position = "absolute";
code.appendChild(node);
- node.style.left = left + "px";
- if (where == "over") top = pos.y;
- else if (where == "near") {
- var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
+ if (vert == "over") top = pos.y;
+ else if (vert == "near") {
+ var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
top = pos.y - node.offsetHeight;
@@ -170,12 +205,20 @@ var CodeMirror = (function() {
left = hspace - node.offsetWidth;
}
node.style.top = (top + paddingTop()) + "px";
- node.style.left = (left + paddingLeft()) + "px";
+ node.style.left = node.style.right = "";
+ if (horiz == "right") {
+ left = code.clientWidth - node.offsetWidth;
+ node.style.right = "0px";
+ } else {
+ if (horiz == "left") left = 0;
+ else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
+ node.style.left = (left + paddingLeft()) + "px";
+ }
if (scroll)
scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
},
- lineCount: function() {return lines.length;},
+ lineCount: function() {return doc.size;},
getCursor: function(start) {
if (start == null) start = sel.inverted;
return copyPos(start ? sel.from : sel.to);
@@ -186,9 +229,9 @@ var CodeMirror = (function() {
else setCursor(line, ch);
}),
setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}),
- getLine: function(line) {if (isLine(line)) return lines[line].text;},
+ getLine: function(line) {if (isLine(line)) return getLine(line).text;},
setLine: operation(function(line, text) {
- if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length});
+ if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
}),
removeLine: operation(function(line) {
if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
@@ -196,24 +239,41 @@ var CodeMirror = (function() {
replaceRange: operation(replaceRange),
getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
+ coordsFromIndex: function(off) {
+ var lineNo = 0, ch;
+ doc.iter(0, doc.size, function(line) {
+ var sz = line.text.length + 1;
+ if (sz > off) { ch = off; return true; }
+ off -= sz;
+ ++lineNo;
+ });
+ return clipPos({line: lineNo, ch: ch});
+ },
+
operation: function(f){return operation(f)();},
refresh: function(){updateDisplay(true);},
getInputField: function(){return input;},
getWrapperElement: function(){return wrapper;},
- getScrollerElement: function(){return scroller;}
+ getScrollerElement: function(){return scroller;},
+ getGutterElement: function(){return gutter;}
};
+ function getLine(n) { return getLineAt(doc, n); }
+ function updateLineHeight(line, height) {
+ gutterDirty = true;
+ var diff = height - line.height;
+ for (var n = line; n; n = n.parent) n.height += diff;
+ }
+
function setValue(code) {
- history = null;
var top = {line: 0, ch: 0};
- updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
+ updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
splitLines(code), top, top);
- history = new History();
+ updateInput = true;
}
function getValue(code) {
var text = [];
- for (var i = 0, l = lines.length; i < l; ++i)
- text.push(lines[i].text);
+ doc.iter(0, doc.size, function(line) { text.push(line.text); });
return text.join("\n");
}
@@ -221,17 +281,17 @@ var CodeMirror = (function() {
// Check whether this is a click in a widget
for (var n = e_target(e); n != wrapper; n = n.parentNode)
if (n.parentNode == code && n != mover) return;
- var ld = lastDoubleClick; lastDoubleClick = null;
- // First, see if this is a click in the gutter
+
+ // See if this is a click in the gutter
for (var n = e_target(e); n != wrapper; n = n.parentNode)
if (n.parentNode == gutterText) {
if (options.onGutterClick)
- options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom);
+ options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
return e_preventDefault(e);
}
var start = posFromMouse(e);
-
+
switch (e_button(e)) {
case 3:
if (gecko && !mac) onContextMenu(e);
@@ -246,18 +306,39 @@ var CodeMirror = (function() {
if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
if (!focused) onFocus();
- e_preventDefault(e);
- if (ld && +new Date - ld < 400) return selectLine(start.line);
- setCursor(start.line, start.ch, true);
+ var now = +new Date;
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+ e_preventDefault(e);
+ setTimeout(focusInput, 20);
+ return selectLine(start.line);
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+ lastDoubleClick = {time: now, pos: start};
+ e_preventDefault(e);
+ return selectWordAt(start);
+ } else { lastClick = {time: now, pos: start}; }
+
var last = start, going;
- // And then we have to see if it's a drag event, in which case
- // the dragged-over text must be selected.
- function end() {
- focusInput();
- updateInput = true;
- move(); up();
+ if (dragAndDrop && !posEq(sel.from, sel.to) &&
+ !posLess(start, sel.from) && !posLess(sel.to, start)) {
+ // Let the drag handler handle this.
+ if (webkit) lineSpace.draggable = true;
+ var up = connect(targetDocument, "mouseup", operation(function(e2) {
+ if (webkit) lineSpace.draggable = false;
+ draggingText = false;
+ up();
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+ e_preventDefault(e2);
+ setCursor(start.line, start.ch, true);
+ focusInput();
+ }
+ }), true);
+ draggingText = true;
+ return;
}
+ e_preventDefault(e);
+ setCursor(start.line, start.ch, true);
+
function extend(e) {
var cur = posFromMouse(e, true);
if (cur && !posEq(cur, last)) {
@@ -281,15 +362,19 @@ var CodeMirror = (function() {
var cur = posFromMouse(e);
if (cur) setSelectionUser(start, cur);
e_preventDefault(e);
- end();
+ focusInput();
+ updateInput = true;
+ move(); up();
}), true);
}
- function onDblClick(e) {
- var pos = posFromMouse(e);
- if (!pos) return;
- selectWordAt(pos);
+ function onDoubleClick(e) {
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
+ if (n.parentNode == gutterText) return e_preventDefault(e);
+ var start = posFromMouse(e);
+ if (!start) return;
+ lastDoubleClick = {time: +new Date, pos: start};
e_preventDefault(e);
- lastDoubleClick = +new Date;
+ selectWordAt(start);
}
function onDrop(e) {
e.preventDefault();
@@ -300,7 +385,13 @@ var CodeMirror = (function() {
var reader = new FileReader;
reader.onload = function() {
text[i] = reader.result;
- if (++read == n) replaceRange(text.join(""), clipPos(pos), clipPos(pos));
+ if (++read == n) {
+ pos = clipPos(pos);
+ operation(function() {
+ var end = replaceRange(text.join(""), pos, pos);
+ setSelectionUser(pos, end);
+ })();
+ }
};
reader.readAsText(file);
}
@@ -310,11 +401,24 @@ var CodeMirror = (function() {
else {
try {
var text = e.dataTransfer.getData("Text");
- if (text) replaceRange(text, pos, pos);
+ if (text) {
+ var end = replaceRange(text, pos, pos);
+ var curFrom = sel.from, curTo = sel.to;
+ setSelectionUser(pos, end);
+ if (draggingText) replaceRange("", curFrom, curTo);
+ focusInput();
+ }
}
catch(e){}
}
}
+ function onDragStart(e) {
+ var txt = getSelection();
+ // This will reset escapeElement
+ htmlEscape(txt);
+ e.dataTransfer.setDragImage(escapeElement, 0, 0);
+ e.dataTransfer.setData("Text", txt);
+ }
function onKeyDown(e) {
if (!focused) onFocus();
@@ -340,6 +444,7 @@ var CodeMirror = (function() {
if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z
if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y
}
+ if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } }
// Key id to use in the movementKeys map. We also pass it to
// fastPoll in order to 'self learn'. We need this because
@@ -347,15 +452,19 @@ var CodeMirror = (function() {
// its start when it is inverted and a movement key is pressed
// (and later restore it again), shouldn't be used for
// non-movement keys.
- curKeyId = (mod ? "c" : "") + code;
- if (sel.inverted && movementKeys.hasOwnProperty(curKeyId)) {
+ curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code;
+ if (sel.inverted && movementKeys[curKeyId] === true) {
var range = selRange(input);
if (range) {
reducedSelection = {anchor: range.start};
setSelRange(input, range.start, range.start);
}
}
+ // Don't save the key as a movementkey unless it had a modifier
+ if (!mod && !e.altKey) curKeyId = null;
fastPoll(curKeyId);
+
+ if (options.pollForIME && keyMightStartIME(code)) slowPollInterval = 50;
}
function onKeyUp(e) {
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
@@ -364,6 +473,8 @@ var CodeMirror = (function() {
updateInput = true;
}
if (e.keyCode == 16) shiftSelecting = null;
+
+ if (slowPollInterval < 2000 && !alwaysPollForIME) slowPollInterval = 2000;
}
function onKeyPress(e) {
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
@@ -406,7 +517,7 @@ var CodeMirror = (function() {
function updateLines(from, to, newText, selFrom, selTo) {
if (history) {
var old = [];
- for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text);
+ doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
history.addChange(from.line, newText.length, old);
while (history.done.length > options.undoDepth) history.done.shift();
}
@@ -416,11 +527,11 @@ var CodeMirror = (function() {
var change = from.pop();
if (change) {
var replaced = [], end = change.start + change.added;
- for (var i = change.start; i < end; ++i) replaced.push(lines[i].text);
+ doc.iter(change.start, end, function(line) { replaced.push(line.text); });
to.push({start: change.start, added: change.old.length, old: replaced});
var pos = clipPos({line: change.start + change.old.length - 1,
ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos);
+ updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
updateInput = true;
}
}
@@ -429,51 +540,66 @@ var CodeMirror = (function() {
function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
var recomputeMaxLength = false, maxLineLength = maxLine.length;
- for (var i = from.line; i <= to.line; ++i) {
- if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;}
- }
+ if (!options.lineWrapping)
+ doc.iter(from.line, to.line, function(line) {
+ if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
+ });
- var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
+ var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
// First adjust the line structure, taking some care to leave highlighting intact.
if (firstLine == lastLine) {
if (newText.length == 1)
firstLine.replace(from.ch, to.ch, newText[0]);
else {
lastLine = firstLine.split(to.ch, newText[newText.length-1]);
- var spliceargs = [from.line + 1, nlines];
- firstLine.replace(from.ch, firstLine.text.length, newText[0]);
- for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
- spliceargs.push(lastLine);
- lines.splice.apply(lines, spliceargs);
+ firstLine.replace(from.ch, null, newText[0]);
+ firstLine.fixMarkEnds(lastLine);
+ var added = [];
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
+ added.push(Line.inheritMarks(newText[i], firstLine));
+ added.push(lastLine);
+ doc.insert(from.line + 1, added);
}
}
else if (newText.length == 1) {
- firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
- lines.splice(from.line + 1, nlines);
+ firstLine.replace(from.ch, null, newText[0]);
+ lastLine.replace(null, to.ch, "");
+ firstLine.append(lastLine);
+ doc.remove(from.line + 1, nlines);
}
else {
- var spliceargs = [from.line + 1, nlines - 1];
- firstLine.replace(from.ch, firstLine.text.length, newText[0]);
- lastLine.replace(0, to.ch, newText[newText.length-1]);
- for (var i = 1, e = newText.length - 1; i < e; ++i) spliceargs.push(new Line(newText[i]));
- lines.splice.apply(lines, spliceargs);
- }
-
-
- for (var i = from.line, e = i + newText.length; i < e; ++i) {
- var l = lines[i].text;
- if (l.length > maxLineLength) {
- maxLine = l; maxLineLength = l.length; maxWidth = null;
- recomputeMaxLength = false;
- }
- }
- if (recomputeMaxLength) {
- maxLineLength = 0; maxLine = ""; maxWidth = null;
- for (var i = 0, e = lines.length; i < e; ++i) {
- var l = lines[i].text;
+ var added = [];
+ firstLine.replace(from.ch, null, newText[0]);
+ lastLine.replace(null, to.ch, newText[newText.length-1]);
+ firstLine.fixMarkEnds(lastLine);
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
+ added.push(Line.inheritMarks(newText[i], firstLine));
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
+ doc.insert(from.line + 1, added);
+ }
+ if (options.lineWrapping) {
+ var perLine = scroller.clientWidth / charWidth() - 3;
+ doc.iter(from.line, from.line + newText.length, function(line) {
+ if (line.hidden) return;
+ var guess = Math.ceil(line.text.length / perLine) || 1;
+ if (guess != line.height) updateLineHeight(line, guess);
+ });
+ } else {
+ doc.iter(from.line, i + newText.length, function(line) {
+ var l = line.text;
if (l.length > maxLineLength) {
- maxLineLength = l.length; maxLine = l;
+ maxLine = l; maxLineLength = l.length; maxWidth = null;
+ recomputeMaxLength = false;
}
+ });
+ if (recomputeMaxLength) {
+ maxLineLength = 0; maxLine = ""; maxWidth = null;
+ doc.iter(0, doc.size, function(line) {
+ var l = line.text;
+ if (l.length > maxLineLength) {
+ maxLineLength = l.length; maxLine = l;
+ }
+ });
}
}
@@ -485,12 +611,9 @@ var CodeMirror = (function() {
if (task < from.line) newWork.push(task);
else if (task > to.line) newWork.push(task + lendiff);
}
- if (newText.length < 5) {
- highlightLines(from.line, from.line + newText.length);
- newWork.push(from.line + newText.length);
- } else {
- newWork.push(from.line);
- }
+ var hlEnd = from.line + Math.min(newText.length, 500);
+ highlightLines(from.line, hlEnd);
+ newWork.push(hlEnd);
work = newWork;
startWorker(100);
// Remember that these lines changed, for updating the display
@@ -502,7 +625,7 @@ var CodeMirror = (function() {
setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
// Make sure the scroll-size div has the correct height.
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
+ code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
}
function replaceRange(code, from, to) {
@@ -540,10 +663,10 @@ var CodeMirror = (function() {
function getRange(from, to) {
var l1 = from.line, l2 = to.line;
- if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch);
- var code = [lines[l1].text.slice(from.ch)];
- for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text);
- code.push(lines[l2].text.slice(0, to.ch));
+ if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
+ var code = [getLine(l1).text.slice(from.ch)];
+ doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
+ code.push(getLine(l2).text.slice(0, to.ch));
return code.join("\n");
}
function getSelection() {
@@ -553,7 +676,7 @@ var CodeMirror = (function() {
var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
function slowPoll() {
if (pollingFast) return;
- poll.set(2000, function() {
+ poll.set(slowPollInterval, function() {
startOperation();
readInput();
if (focused) slowPoll();
@@ -566,7 +689,10 @@ var CodeMirror = (function() {
function p() {
startOperation();
var changed = readInput();
- if (changed == "moved" && keyId) movementKeys[keyId] = true;
+ if (changed && keyId) {
+ if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true;
+ if (changed == "changed") movementKeys[keyId] = false;
+ }
if (!changed && !missed) {missed = true; poll.set(80, p);}
else {pollingFast = false; slowPoll();}
endOperation();
@@ -648,14 +774,16 @@ var CodeMirror = (function() {
// editor state.
function prepareInput() {
var text = [];
- var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2);
- for (var i = from; i < to; ++i) text.push(lines[i].text);
+ var from = Math.max(0, sel.from.line - 1), to = Math.min(doc.size, sel.to.line + 2);
+ doc.iter(from, to, function(line) { text.push(line.text); });
text = input.value = text.join(lineSep);
var startch = sel.from.ch, endch = sel.to.ch;
- for (var i = from; i < sel.from.line; ++i)
- startch += lineSep.length + lines[i].text.length;
- for (var i = from; i < sel.to.line; ++i)
- endch += lineSep.length + lines[i].text.length;
+ doc.iter(from, sel.from.line, function(line) {
+ startch += lineSep.length + line.text.length;
+ });
+ doc.iter(from, sel.to.line, function(line) {
+ endch += lineSep.length + line.text.length;
+ });
editing = {text: text, from: from, to: to, start: startch, end: endch};
setSelRange(input, startch, reducedSelection ? startch : endch);
}
@@ -663,21 +791,29 @@ var CodeMirror = (function() {
if (options.readOnly != "nocursor") input.focus();
}
+ function scrollEditorIntoView() {
+ if (!cursor.getBoundingClientRect) return;
+ var rect = cursor.getBoundingClientRect();
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+ if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
+ }
function scrollCursorIntoView() {
var cursor = localCoords(sel.inverted ? sel.from : sel.to);
- return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot);
+ var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
+ return scrollIntoView(x, cursor.y, x, cursor.yBot);
}
function scrollIntoView(x1, y1, x2, y2) {
- var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
+ var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();
y1 += pt; y2 += pt; x1 += pl; x2 += pl;
var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}
else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}
var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
- if (x1 < screenleft) {
+ var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
+ if (x1 < screenleft + gutterw) {
if (x1 < 50) x1 = 0;
- scroller.scrollLeft = Math.max(0, x1 - 10);
+ scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
scrolled = true;
}
else if (x2 > screenw + screenleft) {
@@ -690,32 +826,103 @@ var CodeMirror = (function() {
}
function visibleLines() {
- var lh = lineHeight(), top = scroller.scrollTop - paddingTop();
- return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))),
- to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
+ var lh = textHeight(), top = scroller.scrollTop - paddingTop();
+ var from_height = Math.max(0, Math.floor(top / lh));
+ var to_height = Math.ceil((top + scroller.clientHeight) / lh);
+ return {from: lineAtHeight(doc, from_height),
+ to: lineAtHeight(doc, to_height)};
}
// Uses a set of changes plus the current scroll position to
// determine which DOM updates have to be made, and makes the
// updates.
function updateDisplay(changes) {
if (!scroller.clientWidth) {
- showingFrom = showingTo = 0;
+ showingFrom = showingTo = displayOffset = 0;
return;
}
- // First create a range of theoretically intact lines, and punch
- // holes in that using the change info.
- var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}];
+ // Compute the new visible window
+ var visible = visibleLines();
+ // Bail out if the visible area is already rendered and nothing changed.
+ if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;
+ var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
+ if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
+ if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
+
+ // Create a range of theoretically intact lines, and punch holes
+ // in that using the change info.
+ var intact = changes === true ? [] :
+ computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
+ // Clip off the parts that won't be visible
+ var intactLines = 0;
+ for (var i = 0; i < intact.length; ++i) {
+ var range = intact[i];
+ if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
+ if (range.to > to) range.to = to;
+ if (range.from >= range.to) intact.splice(i--, 1);
+ else intactLines += range.to - range.from;
+ }
+ if (intactLines == to - from) return;
+ intact.sort(function(a, b) {return a.domStart - b.domStart;});
+
+ var th = textHeight(), gutterDisplay = gutter.style.display;
+ lineDiv.style.display = gutter.style.display = "none";
+ patchDisplay(from, to, intact);
+ lineDiv.style.display = "";
+
+ // Position the mover div to align with the lines it's supposed
+ // to be showing (which will cover the visible display)
+ var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
+ if (different) lastHeight = scroller.clientHeight;
+ showingFrom = from; showingTo = to;
+ displayOffset = heightAtLine(doc, from);
+ mover.style.top = (displayOffset * th) + "px";
+ code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
+
+ // Since this is all rather error prone, it is honoured with the
+ // only assertion in the whole file.
+ if (lineDiv.childNodes.length != showingTo - showingFrom)
+ throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
+ " nodes=" + lineDiv.childNodes.length);
+
+ if (options.lineWrapping) {
+ maxWidth = scroller.clientWidth;
+ var curNode = lineDiv.firstChild;
+ doc.iter(showingFrom, showingTo, function(line) {
+ if (!line.hidden) {
+ var height = Math.round(curNode.offsetHeight / th) || 1;
+ if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}
+ }
+ curNode = curNode.nextSibling;
+ });
+ } else {
+ if (maxWidth == null) maxWidth = stringWidth(maxLine);
+ if (maxWidth > scroller.clientWidth) {
+ lineSpace.style.width = maxWidth + "px";
+ // Needed to prevent odd wrapping/hiding of widgets placed in here.
+ code.style.width = "";
+ code.style.width = scroller.scrollWidth + "px";
+ } else {
+ lineSpace.style.width = code.style.width = "";
+ }
+ }
+ gutter.style.display = gutterDisplay;
+ if (different || gutterDirty) updateGutter();
+ updateCursor();
+ }
+
+ function computeIntact(intact, changes) {
for (var i = 0, l = changes.length || 0; i < l; ++i) {
var change = changes[i], intact2 = [], diff = change.diff || 0;
for (var j = 0, l2 = intact.length; j < l2; ++j) {
var range = intact[j];
- if (change.to <= range.from)
- intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart});
- else if (range.to <= change.from)
+ if (change.to <= range.from && change.diff)
+ intact2.push({from: range.from + diff, to: range.to + diff,
+ domStart: range.domStart});
+ else if (change.to <= range.from || change.from >= range.to)
intact2.push(range);
else {
if (change.from > range.from)
- intact2.push({from: range.from, to: change.from, domStart: range.domStart})
+ intact2.push({from: range.from, to: change.from, domStart: range.domStart});
if (change.to < range.to)
intact2.push({from: change.to + diff, to: range.to + diff,
domStart: range.domStart + (change.to - range.from)});
@@ -723,158 +930,90 @@ var CodeMirror = (function() {
}
intact = intact2;
}
+ return intact;
+ }
- // Then, determine which lines we'd want to see, and which
- // updates have to be made to get there.
- var visible = visibleLines();
- var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)),
- to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)),
- updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0;
-
- for (var i = 0, l = intact.length; i < l; ++i) {
- var range = intact[i];
- if (range.to <= from) continue;
- if (range.from >= to) break;
- if (range.domStart > domPos || range.from > pos) {
- updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos});
- changedLines += range.from - pos;
+ function patchDisplay(from, to, intact) {
+ // The first pass removes the DOM nodes that aren't intact.
+ if (!intact.length) lineDiv.innerHTML = "";
+ else {
+ function killNode(node) {
+ var tmp = node.nextSibling;
+ node.parentNode.removeChild(node);
+ return tmp;
}
- pos = range.to;
- domPos = range.domStart + (range.to - range.from);
- }
- if (domPos != domEnd || pos != to) {
- changedLines += Math.abs(to - pos);
- updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
- }
-
- if (!updates.length) return;
- lineDiv.style.display = "none";
- // If more than 30% of the screen needs update, just do a full
- // redraw (which is quicker than patching)
- if (changedLines > (visible.to - visible.from) * .3)
- refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length));
- // Otherwise, only update the stuff that needs updating.
- else
- patchDisplay(updates);
- lineDiv.style.display = "";
-
- // Position the mover div to align with the lines it's supposed
- // to be showing (which will cover the visible display)
- var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
- showingFrom = from; showingTo = to;
- mover.style.top = (from * lineHeight()) + "px";
- if (different) {
- lastHeight = scroller.clientHeight;
- code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
- updateGutter();
- }
-
- if (maxWidth == null) maxWidth = stringWidth(maxLine);
- if (maxWidth > scroller.clientWidth) {
- lineSpace.style.width = maxWidth + "px";
- // Needed to prevent odd wrapping/hiding of widgets placed in here.
- code.style.width = "";
- code.style.width = scroller.scrollWidth + "px";
- } else {
- lineSpace.style.width = code.style.width = "";
+ var domPos = 0, curNode = lineDiv.firstChild, n;
+ for (var i = 0; i < intact.length; ++i) {
+ var cur = intact[i];
+ while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
+ for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
+ }
+ while (curNode) curNode = killNode(curNode);
}
-
- // Since this is all rather error prone, it is honoured with the
- // only assertion in the whole file.
- if (lineDiv.childNodes.length != showingTo - showingFrom)
- throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) +
- " nodes=" + lineDiv.childNodes.length);
- updateCursor();
- }
-
- function refreshDisplay(from, to) {
- var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start);
- for (var i = from; i < to; ++i) {
+ // This pass fills in the lines that actually changed.
+ var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
+ var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;
+ var scratch = targetDocument.createElement("div"), newElt;
+ doc.iter(from, to, function(line) {
var ch1 = null, ch2 = null;
if (inSel) {
ch1 = 0;
- if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;}
- }
- else if (sel.from.line == i) {
- if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
+ if (sto == j) {inSel = false; ch2 = sel.to.ch;}
+ } else if (sfrom == j) {
+ if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
else {inSel = true; ch1 = sel.from.ch;}
}
- html.push(lines[i].getHTML(ch1, ch2, true));
- }
- lineDiv.innerHTML = html.join("");
- }
- function patchDisplay(updates) {
- // Slightly different algorithm for IE (badInnerHTML), since
- // there .innerHTML on PRE nodes is dumb, and discards
- // whitespace.
- var sfrom = sel.from.line, sto = sel.to.line, off = 0,
- scratch = badInnerHTML && targetDocument.createElement("div");
- for (var i = 0, e = updates.length; i < e; ++i) {
- var rec = updates[i];
- var extra = (rec.to - rec.from) - rec.domSize;
- var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null;
- if (badInnerHTML)
- for (var j = Math.max(-extra, rec.domSize); j > 0; --j)
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
- else if (extra) {
- for (var j = Math.max(0, extra); j > 0; --j)
- lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
- for (var j = Math.max(0, -extra); j > 0; --j)
- lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
- }
- var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from;
- for (var j = rec.from; j < rec.to; ++j) {
- var ch1 = null, ch2 = null;
- if (inSel) {
- ch1 = 0;
- if (sto == j) {inSel = false; ch2 = sel.to.ch;}
- }
- else if (sfrom == j) {
- if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}
- else {inSel = true; ch1 = sel.from.ch;}
- }
- if (badInnerHTML) {
- scratch.innerHTML = lines[j].getHTML(ch1, ch2, true);
- lineDiv.insertBefore(scratch.firstChild, nodeAfter);
- }
- else {
- node.innerHTML = lines[j].getHTML(ch1, ch2, false);
- node.className = lines[j].className || "";
- node = node.nextSibling;
- }
+ if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
+ if (!nextIntact || nextIntact.from > j) {
+ if (line.hidden) scratch.innerHTML = "<pre></pre>";
+ else scratch.innerHTML = line.getHTML(ch1, ch2, true);
+ lineDiv.insertBefore(scratch.firstChild, curNode);
+ } else {
+ curNode = curNode.nextSibling;
}
- off += extra;
- }
+ ++j;
+ });
}
function updateGutter() {
if (!options.gutter && !options.lineNumbers) return;
var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
- var html = [];
- for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) {
- var marker = lines[i].gutterMarker;
- var text = options.lineNumbers ? i + options.firstLineNumber : null;
- if (marker && marker.text)
- text = marker.text.replace("%N%", text != null ? text : "");
- else if (text == null)
- text = "\u00a0";
- html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text, "</pre>");
- }
+ var html = [], i = showingFrom;
+ doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
+ if (line.hidden) {
+ html.push("<pre></pre>");
+ } else {
+ var marker = line.gutterMarker;
+ var text = options.lineNumbers ? i + options.firstLineNumber : null;
+ if (marker && marker.text)
+ text = marker.text.replace("%N%", text != null ? text : "");
+ else if (text == null)
+ text = "\u00a0";
+ html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
+ for (var j = 1; j < line.height; ++j) html.push("<br>&nbsp;");
+ html.push("</pre>");
+ }
+ ++i;
+ });
gutter.style.display = "none";
gutterText.innerHTML = html.join("");
- var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
+ var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
while (val.length + pad.length < minwidth) pad += "\u00a0";
if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);
gutter.style.display = "";
lineSpace.style.marginLeft = gutter.offsetWidth + "px";
+ gutterDirty = false;
}
function updateCursor() {
- var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
- var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lh + "px";
- inputDiv.style.top = (head.line * lh - scroller.scrollTop) + "px";
+ var head = sel.inverted ? sel.from : sel.to, lh = textHeight();
+ var pos = localCoords(head, true);
+ var globalY = pos.y + displayOffset * textHeight();
+ inputDiv.style.top = Math.max(Math.min(globalY, scroller.offsetHeight), 0) + "px";
+ inputDiv.style.left = (pos.x - scroller.scrollLeft) + "px";
if (posEq(sel.from, sel.to)) {
- cursor.style.top = y; cursor.style.left = x;
+ cursor.style.top = pos.y + "px";
+ cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px";
cursor.style.display = "";
}
else cursor.style.display = "none";
@@ -892,9 +1031,14 @@ var CodeMirror = (function() {
// updateLines, since they have to be expressed in the line
// numbers before the update.
function setSelection(from, to, oldFrom, oldTo) {
+ if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
if (posEq(sel.from, from) && posEq(sel.to, to)) return;
if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
+ // Skip over hidden lines.
+ if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);
+ if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
+
if (posEq(from, to)) sel.inverted = false;
else if (posEq(from, sel.to)) sel.inverted = false;
else if (posEq(to, sel.from)) sel.inverted = true;
@@ -902,7 +1046,6 @@ var CodeMirror = (function() {
// Some ugly logic used to only mark the lines that actually did
// see a change in selection as changed, rather than the whole
// selected range.
- if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
if (posEq(from, to)) {
if (!posEq(sel.from, sel.to))
changes.push({from: oldFrom, to: oldTo + 1});
@@ -927,42 +1070,61 @@ var CodeMirror = (function() {
sel.from = from; sel.to = to;
selectionChanged = true;
}
+ function skipHidden(pos, oldLine, oldCh) {
+ function getNonHidden(dir) {
+ var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
+ while (lNo != end) {
+ var line = getLine(lNo);
+ if (!line.hidden) {
+ var ch = pos.ch;
+ if (ch > oldCh || ch > line.text.length) ch = line.text.length;
+ return {line: lNo, ch: ch};
+ }
+ lNo += dir;
+ }
+ }
+ var line = getLine(pos.line);
+ if (!line.hidden) return pos;
+ if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
+ else return getNonHidden(-1) || getNonHidden(1);
+ }
function setCursor(line, ch, user) {
var pos = clipPos({line: line, ch: ch || 0});
(user ? setSelectionUser : setSelection)(pos, pos);
}
- function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
+ function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
function clipPos(pos) {
if (pos.line < 0) return {line: 0, ch: 0};
- if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length};
- var ch = pos.ch, linelen = lines[pos.line].text.length;
+ if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
+ var ch = pos.ch, linelen = getLine(pos.line).text.length;
if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
else if (ch < 0) return {line: pos.line, ch: 0};
else return pos;
}
function scrollPage(down) {
- var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
- setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true);
+ var linesPerPage = Math.floor(scroller.clientHeight / textHeight()), head = sel.inverted ? sel.from : sel.to;
+ var target = heightAtLine(doc, head.line) + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1));
+ setCursor(lineAtHeight(doc, target), head.ch, true);
}
function scrollEnd(top) {
- var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
+ var pos = top ? {line: 0, ch: 0} : {line: doc.size - 1, ch: getLine(doc.size-1).text.length};
setSelectionUser(pos, pos);
}
function selectAll() {
- var endLine = lines.length - 1;
- setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length});
+ var endLine = doc.size - 1;
+ setSelection({line: 0, ch: 0}, {line: endLine, ch: getLine(endLine).text.length});
}
function selectWordAt(pos) {
- var line = lines[pos.line].text;
+ var line = getLine(pos.line).text;
var start = pos.ch, end = pos.ch;
while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
while (end < line.length && /\w/.test(line.charAt(end))) ++end;
setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
}
function selectLine(line) {
- setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length});
+ setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
}
function handleEnter() {
replaceSelection("\n", "end");
@@ -994,6 +1156,10 @@ var CodeMirror = (function() {
}
return true;
}
+ function smartHome() {
+ var firstNonWS = Math.max(0, getLine(sel.from.line).text.search(/\S/));
+ setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true);
+ }
function indentLine(n, how) {
if (how == "smart") {
@@ -1001,9 +1167,9 @@ var CodeMirror = (function() {
else var state = getStateBefore(n);
}
- var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
+ var line = getLine(n), curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation;
if (how == "prev") {
- if (n) indentation = lines[n-1].indentation();
+ if (n) indentation = getLine(n-1).indentation();
else indentation = 0;
}
else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length));
@@ -1028,87 +1194,148 @@ var CodeMirror = (function() {
function loadMode() {
mode = CodeMirror.getMode(options, options.mode);
- for (var i = 0, l = lines.length; i < l; ++i)
- lines[i].stateAfter = null;
+ doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
work = [0];
startWorker();
}
function gutterChanged() {
var visible = options.gutter || options.lineNumbers;
gutter.style.display = visible ? "" : "none";
- if (visible) updateGutter();
+ if (visible) gutterDirty = true;
else lineDiv.parentNode.style.marginLeft = 0;
}
+ function wrappingChanged(from, to) {
+ if (options.lineWrapping) {
+ wrapper.className += " CodeMirror-wrap";
+ var perLine = scroller.clientWidth / charWidth() - 3;
+ doc.iter(0, doc.size, function(line) {
+ if (line.hidden) return;
+ var guess = Math.ceil(line.text.length / perLine) || 1;
+ if (guess != 1) updateLineHeight(line, guess);
+ });
+ lineSpace.style.width = code.style.width = "";
+ } else {
+ wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
+ maxWidth = null; maxLine = "";
+ doc.iter(0, doc.size, function(line) {
+ if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
+ if (line.text.length > maxLine.length) maxLine = line.text;
+ });
+ }
+ changes.push({from: 0, to: doc.size});
+ }
+
+ function TextMarker() { this.set = []; }
+ TextMarker.prototype.clear = operation(function() {
+ for (var i = 0, e = this.set.length; i < e; ++i) {
+ var mk = this.set[i].marked;
+ if (!mk) continue;
+ for (var j = 0; j < mk.length; ++j)
+ if (mk[j].set == this.set) mk.splice(j--, 1);
+ }
+ // We don't know the exact lines that changed. Refreshing is
+ // cheaper than finding them.
+ changes.push({from: 0, to: doc.size});
+ });
+ TextMarker.prototype.find = function() {
+ var from, to;
+ for (var i = 0, e = this.set.length; i < e; ++i) {
+ var line = this.set[i], mk = line.marked;
+ for (var j = 0; j < mk.length; ++j) {
+ var mark = mk[j];
+ if (mark.set == this.set) {
+ if (mark.from != null || mark.to != null) {
+ var found = lineNo(line);
+ if (found != null) {
+ if (mark.from != null) from = {line: found, ch: mark.from};
+ if (mark.to != null) to = {line: found, ch: mark.to};
+ }
+ }
+ }
+ }
+ }
+ return {from: from, to: to};
+ };
function markText(from, to, className) {
from = clipPos(from); to = clipPos(to);
- var accum = [];
+ var tm = new TextMarker();
function add(line, from, to, className) {
- var line = lines[line], mark = line.addMark(from, to, className);
- mark.line = line;
- accum.push(mark);
+ mark = getLine(line).addMark(new MarkedText(from, to, className, tm.set));
}
if (from.line == to.line) add(from.line, from.ch, to.ch, className);
else {
add(from.line, from.ch, null, className);
for (var i = from.line + 1, e = to.line; i < e; ++i)
- add(i, 0, null, className);
- add(to.line, 0, to.ch, className);
+ add(i, null, null, className);
+ add(to.line, null, to.ch, className);
}
changes.push({from: from.line, to: to.line + 1});
- return function() {
- var start, end;
- for (var i = 0; i < accum.length; ++i) {
- var mark = accum[i], found = indexOf(lines, mark.line);
- mark.line.removeMark(mark);
- if (found > -1) {
- if (start == null) start = found;
- end = found;
- }
- }
- if (start != null) changes.push({from: start, to: end + 1});
- };
+ return tm;
+ }
+
+ function setBookmark(pos) {
+ pos = clipPos(pos);
+ var bm = new Bookmark(pos.ch);
+ getLine(pos.line).addMark(bm);
+ return bm;
}
function addGutterMarker(line, text, className) {
- if (typeof line == "number") line = lines[clipLine(line)];
+ if (typeof line == "number") line = getLine(clipLine(line));
line.gutterMarker = {text: text, style: className};
- updateGutter();
+ gutterDirty = true;
return line;
}
function removeGutterMarker(line) {
- if (typeof line == "number") line = lines[clipLine(line)];
+ if (typeof line == "number") line = getLine(clipLine(line));
line.gutterMarker = null;
- updateGutter();
+ gutterDirty = true;
}
- function setLineClass(line, className) {
- if (typeof line == "number") {
- var no = line;
- line = lines[clipLine(line)];
- }
- else {
- var no = indexOf(lines, line);
- if (no == -1) return null;
- }
- if (line.className != className) {
- line.className = className;
- changes.push({from: no, to: no + 1});
- }
+
+ function changeLine(handle, op) {
+ var no = handle, line = handle;
+ if (typeof handle == "number") line = getLine(clipLine(handle));
+ else no = lineNo(handle);
+ if (no == null) return null;
+ if (op(line, no)) changes.push({from: no, to: no + 1});
return line;
}
+ function setLineClass(handle, className) {
+ return changeLine(handle, function(line) {
+ if (line.className != className) {
+ line.className = className;
+ return true;
+ }
+ });
+ }
+ function setLineHidden(handle, hidden) {
+ return changeLine(handle, function(line, no) {
+ if (line.hidden != hidden) {
+ line.hidden = hidden;
+ updateLineHeight(line, hidden ? 0 : 1);
+ if (hidden && (sel.from.line == no || sel.to.line == no))
+ setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),
+ skipHidden(sel.to, sel.to.line, sel.to.ch));
+ return (gutterDirty = true);
+ }
+ });
+ }
function lineInfo(line) {
if (typeof line == "number") {
+ if (!isLine(line)) return null;
var n = line;
- line = lines[line];
+ line = getLine(line);
if (!line) return null;
}
else {
- var n = indexOf(lines, line);
- if (n == -1) return null;
+ var n = lineNo(line);
+ if (n == null) return null;
}
var marker = line.gutterMarker;
- return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
+ return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
+ markerClass: marker && marker.style, lineClass: line.className};
}
function stringWidth(str) {
@@ -1118,21 +1345,16 @@ var CodeMirror = (function() {
}
// These are used to go from pixel positions to character
// positions, taking varying character widths into account.
- function charX(line, pos) {
- 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) {
if (x <= 0) return 0;
- var lineObj = lines[line], text = lineObj.text;
+ var lineObj = getLine(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")));
+ var estimated = Math.min(to, Math.ceil(x / charWidth()));
for (;;) {
var estX = getX(estimated);
if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
@@ -1151,20 +1373,93 @@ var CodeMirror = (function() {
}
}
+ var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
+ function measureLine(line, ch) {
+ var extra = "";
+ // Include extra text at the end to make sure the measured line is wrapped in the right way.
+ if (options.lineWrapping) {
+ var end = line.text.indexOf(" ", ch + 2);
+ extra = line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0));
+ }
+ measure.innerHTML = "<pre>" + line.getHTML(null, null, false, ch) +
+ '<span id="CodeMirror-temp-' + tempId + '">' + (line.text.charAt(ch) || " ") + "</span>" +
+ extra + "</pre>";
+ var elt = document.getElementById("CodeMirror-temp-" + tempId);
+ var top = elt.offsetTop, left = elt.offsetLeft;
+ // Older IEs report zero offsets for spans directly after a wrap
+ if (ie && ch && top == 0 && left == 0) {
+ var backup = document.createElement("span");
+ backup.innerHTML = "x";
+ elt.parentNode.insertBefore(backup, elt.nextSibling);
+ top = backup.offsetTop;
+ }
+ return {top: top, left: left};
+ }
function localCoords(pos, inLineWrap) {
- var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0);
- return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh};
+ var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
+ if (pos.ch == 0) x = 0;
+ else {
+ var sp = measureLine(getLine(pos.line), pos.ch);
+ x = sp.left;
+ if (options.lineWrapping) y += Math.max(0, sp.top);
+ }
+ return {x: x, y: y, yBot: y + lh};
+ }
+ // Coords must be lineSpace-local
+ function coordsChar(x, y) {
+ if (y < 0) y = 0;
+ var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
+ var lineNo = lineAtHeight(doc, heightPos);
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: 0};
+ var lineObj = getLine(lineNo), text = lineObj.text;
+ var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
+ if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
+ function getX(len) {
+ var sp = measureLine(lineObj, len);
+ if (tw) {
+ var off = Math.round(sp.top / th);
+ return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
+ }
+ return sp.left;
+ }
+ 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 + innerOff * scroller.clientWidth * .9) / cw));
+ 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 {line: lineNo, ch: 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 {line: lineNo, ch: (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 pageCoords(pos) {
var local = localCoords(pos, true), off = eltOffset(lineSpace);
return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
}
- function lineHeight() {
- var nlines = lineDiv.childNodes.length;
- if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
- measure.innerHTML = "<pre>x</pre>";
- return measure.firstChild.offsetHeight || 1;
+ var cachedHeight, cachedFor;
+ function textHeight() {
+ var offsetHeight = lineDiv.offsetHeight;
+ if (offsetHeight == cachedFor) return cachedHeight;
+ cachedFor = offsetHeight;
+ measure.innerHTML = "<pre>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x<br>x</pre>";
+ return (cachedHeight = measure.firstChild.offsetHeight / 10 || 1);
+ }
+ var cachedWidth, cachedFor = 0;
+ function charWidth() {
+ if (scroller.clientWidth == cachedFor) return cachedWidth;
+ cachedFor = scroller.clientWidth;
+ return (cachedWidth = stringWidth("x"));
}
function paddingTop() {return lineSpace.offsetTop;}
function paddingLeft() {return lineSpace.offsetLeft;}
@@ -1179,8 +1474,7 @@ var CodeMirror = (function() {
if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
return null;
var offL = eltOffset(lineSpace, true);
- var line = showingFrom + Math.floor((y - offL.top) / lineHeight());
- return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)});
+ return coordsChar(x - offL.left, y - offL.top);
}
function onContextMenu(e) {
var pos = posFromMouse(e);
@@ -1190,8 +1484,8 @@ var CodeMirror = (function() {
var oldCSS = input.style.cssText;
inputDiv.style.position = "absolute";
- input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e_pageY(e) - 1) +
- "px; left: " + (e_pageX(e) - 1) + "px; z-index: 1000; background: white; " +
+ input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
leaveInputAlone = true;
var val = input.value = getSelection();
@@ -1206,7 +1500,7 @@ var CodeMirror = (function() {
prepareInput();
slowPoll();
}
-
+
if (gecko) {
e_stop(e);
var mouseup = connect(window, "mouseup", function() {
@@ -1231,7 +1525,7 @@ var CodeMirror = (function() {
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
function matchBrackets(autoclear) {
- var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1;
+ var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
if (!match) return;
var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
@@ -1255,18 +1549,16 @@ var CodeMirror = (function() {
}
}
}
- for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) {
- var line = lines[i], first = i == head.line;
+ for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
+ var line = getLine(i), first = i == head.line;
var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
if (found) break;
}
if (!found) found = {pos: null, match: false};
var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
- two = found.pos != null
- ? markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style)
- : function() {};
- var clear = operation(function(){one(); two();});
+ two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
+ var clear = operation(function(){one.clear(); two && two.clear();});
if (autoclear) setTimeout(clear, 800);
else bracketHighlighted = clear;
}
@@ -1280,66 +1572,69 @@ var CodeMirror = (function() {
var minindent, minline;
for (var search = n, lim = n - 40; search > lim; --search) {
if (search == 0) return 0;
- var line = lines[search-1];
+ var line = getLine(search-1);
if (line.stateAfter) return search;
var indented = line.indentation();
if (minline == null || minindent > indented) {
- minline = search;
+ minline = search - 1;
minindent = indented;
}
}
return minline;
}
function getStateBefore(n) {
- var start = findStartLine(n), state = start && lines[start-1].stateAfter;
+ var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
if (!state) state = startState(mode);
else state = copyState(mode, state);
- for (var i = start; i < n; ++i) {
- var line = lines[i];
+ doc.iter(start, n, function(line) {
line.highlight(mode, state);
line.stateAfter = copyState(mode, state);
- }
- if (n < lines.length && !lines[n].stateAfter) work.push(n);
+ });
+ if (start < n) changes.push({from: start, to: n});
+ if (n < doc.size && !getLine(n).stateAfter) work.push(n);
return state;
}
function highlightLines(start, end) {
var state = getStateBefore(start);
- for (var i = start; i < end; ++i) {
- var line = lines[i];
+ doc.iter(start, end, function(line) {
line.highlight(mode, state);
line.stateAfter = copyState(mode, state);
- }
+ });
}
function highlightWorker() {
var end = +new Date + options.workTime;
var foundWork = work.length;
while (work.length) {
- if (!lines[showingFrom].stateAfter) var task = showingFrom;
+ if (!getLine(showingFrom).stateAfter) var task = showingFrom;
else var task = work.pop();
- if (task >= lines.length) continue;
- var start = findStartLine(task), state = start && lines[start-1].stateAfter;
+ if (task >= doc.size) continue;
+ var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
if (state) state = copyState(mode, state);
else state = startState(mode);
- var unchanged = 0, compare = mode.compareStates;
- for (var i = start, l = lines.length; i < l; ++i) {
- var line = lines[i], hadState = line.stateAfter;
+ var unchanged = 0, compare = mode.compareStates, realChange = false,
+ i = start, bail = false;
+ doc.iter(i, doc.size, function(line) {
+ var hadState = line.stateAfter;
if (+new Date > end) {
work.push(i);
startWorker(options.workDelay);
- changes.push({from: task, to: i + 1});
- return;
+ if (realChange) changes.push({from: task, to: i + 1});
+ return (bail = true);
}
var changed = line.highlight(mode, state);
+ if (changed) realChange = true;
line.stateAfter = copyState(mode, state);
if (compare) {
- if (hadState && compare(hadState, state)) break;
+ if (hadState && compare(hadState, state)) return true;
} else {
- if (changed || !hadState) unchanged = 0;
- else if (++unchanged > 3) break;
+ if (changed !== false || !hadState) unchanged = 0;
+ else if (++unchanged > 3) return true;
}
- }
- changes.push({from: task, to: i + 1});
+ ++i;
+ });
+ if (bail) return;
+ if (realChange) changes.push({from: task, to: i + 1});
}
if (foundWork && options.onHighlightComplete)
options.onHighlightComplete(instance);
@@ -1360,9 +1655,12 @@ var CodeMirror = (function() {
var reScroll = false;
if (selectionChanged) reScroll = !scrollCursorIntoView();
if (changes.length) updateDisplay(changes);
- else if (selectionChanged) updateCursor();
+ else {
+ if (selectionChanged) updateCursor();
+ if (gutterDirty) updateGutter();
+ }
if (reScroll) scrollCursorIntoView();
- if (selectionChanged) restartBlink();
+ if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
// updateInput can be set to a boolean value to force/prevent an
// update.
@@ -1406,7 +1704,7 @@ var CodeMirror = (function() {
if (typeof query != "string") // Regexp match
this.matches = function(reverse, pos) {
if (reverse) {
- var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0;
+ var line = getLine(pos.line).text.slice(0, pos.ch), match = line.match(query), start = 0;
while (match) {
var ind = line.indexOf(match[0]);
start += ind;
@@ -1418,7 +1716,7 @@ var CodeMirror = (function() {
}
}
else {
- var line = lines[pos.line].text.slice(pos.ch), match = line.match(query),
+ var line = getLine(pos.line).text.slice(pos.ch), match = line.match(query),
start = match && pos.ch + line.indexOf(match[0]);
}
if (match)
@@ -1433,7 +1731,7 @@ var CodeMirror = (function() {
// Different methods for single-line and multi-line queries
if (target.length == 1)
this.matches = function(reverse, pos) {
- var line = fold(lines[pos.line].text), len = query.length, match;
+ var line = fold(getLine(pos.line).text), len = query.length, match;
if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
: (match = line.indexOf(query, pos.ch)) != -1)
return {from: {line: pos.line, ch: match},
@@ -1441,14 +1739,14 @@ var CodeMirror = (function() {
};
else
this.matches = function(reverse, pos) {
- var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text);
+ var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(getLine(ln).text);
var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
if (reverse ? offsetA >= pos.ch || offsetA != match.length
: offsetA <= pos.ch || offsetA != line.length - match.length)
return;
for (;;) {
- if (reverse ? !ln : ln == lines.length - 1) return;
- line = fold(lines[ln += reverse ? -1 : 1].text);
+ if (reverse ? !ln : ln == doc.size - 1) return;
+ line = fold(getLine(ln += reverse ? -1 : 1).text);
match = target[reverse ? --idx : ++idx];
if (idx > 0 && idx < target.length - 1) {
if (line != match) return;
@@ -1484,10 +1782,10 @@ var CodeMirror = (function() {
}
if (reverse) {
if (!pos.line) return savePosAndFail(0);
- pos = {line: pos.line-1, ch: lines[pos.line-1].text.length};
+ pos = {line: pos.line-1, ch: getLine(pos.line-1).text.length};
}
else {
- if (pos.line == lines.length - 1) return savePosAndFail(lines.length);
+ if (pos.line == doc.size - 1) return savePosAndFail(doc.size);
pos = {line: pos.line+1, ch: 0};
}
}
@@ -1523,10 +1821,13 @@ var CodeMirror = (function() {
enterMode: "indent",
electricChars: true,
onKeyEvent: null,
+ lineWrapping: false,
lineNumbers: false,
gutter: false,
+ fixedGutter: false,
firstLineNumber: 1,
readOnly: false,
+ smartHome: true,
onChange: null,
onCursorActivity: null,
onGutterClick: null,
@@ -1537,6 +1838,7 @@ var CodeMirror = (function() {
workDelay: 200,
undoDepth: 40,
tabindex: null,
+ pollForIME: false,
document: window.document
};
@@ -1572,7 +1874,7 @@ var CodeMirror = (function() {
CodeMirror.listMIMEs = function() {
var list = [];
for (var m in mimeModes)
- if (mimeModes.propertyIsEnumerable(m)) list.push(m);
+ if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
return list;
};
@@ -1634,11 +1936,11 @@ var CodeMirror = (function() {
}
return nstate;
}
- CodeMirror.startState = startState;
+ CodeMirror.copyState = copyState;
function startState(mode, a1, a2) {
return mode.startState ? mode.startState(a1, a2) : true;
}
- CodeMirror.copyState = copyState;
+ CodeMirror.startState = startState;
// The character stream used by a mode's parser.
function StringStream(string) {
@@ -1660,7 +1962,7 @@ var CodeMirror = (function() {
if (ok) {++this.pos; return ch;}
},
eatWhile: function(match) {
- var start = this.start;
+ var start = this.pos;
while (this.eat(match)){}
return this.pos > start;
},
@@ -1695,18 +1997,98 @@ var CodeMirror = (function() {
};
CodeMirror.StringStream = StringStream;
+ function MarkedText(from, to, className, set) {
+ this.from = from; this.to = to; this.style = className; this.set = set;
+ }
+ MarkedText.prototype = {
+ attach: function(line) { this.set.push(line); },
+ detach: function(line) {
+ var ix = indexOf(this.set, line);
+ if (ix > -1) this.set.splice(ix, 1);
+ },
+ split: function(pos, lenBefore) {
+ if (this.to <= pos && this.to != null) return null;
+ var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
+ var to = this.to == null ? null : this.to - pos + lenBefore;
+ return new MarkedText(from, to, this.style, this.set);
+ },
+ dup: function() { return new MarkedText(null, null, this.style, this.set); },
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
+ if (this.from != null && this.from >= from)
+ this.from = Math.max(to, this.from) + diff;
+ if (this.to != null && this.to > from)
+ this.to = to < this.to ? this.to + diff : from;
+ if (fromOpen && to > this.from && (to < this.to || this.to == null))
+ this.from = null;
+ if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
+ this.to = null;
+ },
+ isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
+ sameSet: function(x) { return this.set == x.set; }
+ };
+
+ function Bookmark(pos) {
+ this.from = pos; this.to = pos; this.line = null;
+ }
+ Bookmark.prototype = {
+ attach: function(line) { this.line = line; },
+ detach: function(line) { if (this.line == line) this.line = null; },
+ split: function(pos, lenBefore) {
+ if (pos < this.from) {
+ this.from = this.to = (this.from - pos) + lenBefore;
+ return this;
+ }
+ },
+ isDead: function() { return this.from > this.to; },
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
+ if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
+ this.from = 0; this.to = -1;
+ } else if (this.from > from) {
+ this.from = this.to = Math.max(to, this.from) + diff;
+ }
+ },
+ sameSet: function(x) { return false; },
+ find: function() {
+ if (!this.line || !this.line.parent) return null;
+ return {line: lineNo(this.line), ch: this.from};
+ },
+ clear: function() {
+ if (this.line) {
+ var found = indexOf(this.line.marked, this);
+ if (found != -1) this.line.marked.splice(found, 1);
+ this.line = null;
+ }
+ }
+ };
+
// Line objects. These hold state related to a line, including
// highlighting info (the styles array).
function Line(text, styles) {
this.styles = styles || [text, null];
- this.stateAfter = null;
this.text = text;
+ this.height = 1;
this.marked = this.gutterMarker = this.className = null;
+ this.stateAfter = this.parent = this.hidden = null;
+ }
+ Line.inheritMarks = function(text, orig) {
+ var ln = new Line(text), mk = orig.marked;
+ if (mk) {
+ for (var i = 0; i < mk.length; ++i) {
+ if (mk[i].to == null && mk[i].style) {
+ var newmk = ln.marked || (ln.marked = []), mark = mk[i];
+ var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
+ }
+ }
+ }
+ return ln;
}
Line.prototype = {
// Replace a piece of a line, keeping the styles around it intact.
- replace: function(from, to, text) {
- var st = [], mk = this.marked;
+ replace: function(from, to_, text) {
+ // Reset line class if the whole text was replaced.
+ if (!from && (to_ == null || to_ == this.text.length))
+ this.className = this.gutterMarker = null;
+ var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
copyStyles(0, from, this.styles, st);
if (text) st.push(text, null);
copyStyles(to, this.text.length, this.styles, st);
@@ -1714,34 +2096,79 @@ var CodeMirror = (function() {
this.text = this.text.slice(0, from) + text + this.text.slice(to);
this.stateAfter = null;
if (mk) {
- var diff = text.length - (to - from), end = this.text.length;
- function fix(n) {return n <= Math.min(to, to + diff) ? n : n + diff;}
- for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i], del = false;
- if (mark.from >= end) del = true;
- else {mark.from = fix(mark.from); if (mark.to != null) mark.to = fix(mark.to);}
- if (del || mark.from >= mark.to) {mk.splice(i, 1); i--;}
+ var diff = text.length - (to - from);
+ for (var i = 0, mark = mk[i]; i < mk.length; ++i) {
+ mark.clipTo(from == null, from || 0, to_ == null, to, diff);
+ if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
}
}
},
- // Split a line in two, again keeping styles intact.
+ // Split a part off a line, keeping styles and markers intact.
split: function(pos, textBefore) {
- var st = [textBefore, null];
+ var st = [textBefore, null], mk = this.marked;
copyStyles(pos, this.text.length, this.styles, st);
- return new Line(textBefore + this.text.slice(pos), st);
+ var taken = new Line(textBefore + this.text.slice(pos), st);
+ if (mk) {
+ for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i];
+ var newmark = mark.split(pos, textBefore.length);
+ if (newmark) {
+ if (!taken.marked) taken.marked = [];
+ taken.marked.push(newmark); newmark.attach(taken);
+ }
+ }
+ }
+ return taken;
},
- addMark: function(from, to, style) {
- var mk = this.marked, mark = {from: from, to: to, style: style};
- if (this.marked == null) this.marked = [];
- this.marked.push(mark);
- this.marked.sort(function(a, b){return a.from - b.from;});
- return mark;
+ append: function(line) {
+ var mylen = this.text.length, mk = line.marked, mymk = this.marked;
+ this.text += line.text;
+ copyStyles(0, line.text.length, line.styles, this.styles);
+ if (mymk) {
+ for (var i = 0; i < mymk.length; ++i)
+ if (mymk[i].to == null) mymk[i].to = mylen;
+ }
+ if (mk && mk.length) {
+ if (!mymk) this.marked = mymk = [];
+ outer: for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i];
+ if (!mark.from) {
+ for (var j = 0; j < mymk.length; ++j) {
+ var mymark = mymk[j];
+ if (mymark.to == mylen && mymark.sameSet(mark)) {
+ mymark.to = mark.to == null ? null : mark.to + mylen;
+ if (mymark.isDead()) {
+ mymark.detach(this);
+ mk.splice(i--, 1);
+ }
+ continue outer;
+ }
+ }
+ }
+ mymk.push(mark);
+ mark.attach(this);
+ mark.from += mylen;
+ if (mark.to != null) mark.to += mylen;
+ }
+ }
},
- removeMark: function(mark) {
- var mk = this.marked;
+ fixMarkEnds: function(other) {
+ var mk = this.marked, omk = other.marked;
if (!mk) return;
- for (var i = 0; i < mk.length; ++i)
- if (mk[i] == mark) {mk.splice(i, 1); break;}
+ for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i], close = mark.to == null;
+ if (close && omk) {
+ for (var j = 0; j < omk.length; ++j)
+ if (omk[j].sameSet(mark)) {close = false; break;}
+ }
+ if (close) mark.to = this.text.length;
+ }
+ },
+ addMark: function(mark) {
+ mark.attach(this);
+ if (this.marked == null) this.marked = [];
+ this.marked.push(mark);
+ this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
},
// Run the given mode's parser over a line, update the styles
// array, which contains alternating fragments of text and CSS
@@ -1769,10 +2196,10 @@ var CodeMirror = (function() {
}
if (st.length != pos) {st.length = pos; changed = true;}
if (pos && st[pos-2] != prevWord) changed = true;
- // Short lines with simple highlights always count as changed,
- // because they are likely to highlight the same way in various
- // contexts.
- return changed || (st.length < 5 && this.text.length < 10);
+ // Short lines with simple highlights return null, and are
+ // counted as changed by the driver because they are likely to
+ // highlight the same way in various contexts.
+ return changed || (st.length < 5 && this.text.length < 10 ? null : false);
},
// Fetch the parser token for a given character. Useful for hacks
// that want to inspect the mode state (say, for completion).
@@ -1792,11 +2219,14 @@ var CodeMirror = (function() {
// Produces an HTML fragment for the line, taking selection,
// marking, and highlighting into account.
getHTML: function(sfrom, sto, includePre, endAt) {
- var html = [];
+ var html = [], first = true;
if (includePre)
html.push(this.className ? '<pre class="' + this.className + '">': "<pre>");
function span(text, style) {
if (!text) return;
+ // Work around a bug where, in some compat modes, IE ignores leading spaces
+ if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
+ first = false;
if (style) html.push('<span class="', style, '">', htmlEscape(text), "</span>");
else html.push(htmlEscape(text));
}
@@ -1809,10 +2239,10 @@ var CodeMirror = (function() {
span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
else if (!marked && sfrom == null)
for (var i = 0, ch = 0; ch < len; i+=2) {
- var str = st[i], l = str.length;
+ var str = st[i], style = st[i+1], l = str.length;
if (ch + l > len) str = str.slice(0, len - ch);
ch += l;
- span(str, "cm-" + st[i+1]);
+ span(str, style && "cm-" + style);
}
else {
var pos = 0, i = 0, text = "", style, sg = 0;
@@ -1856,6 +2286,11 @@ var CodeMirror = (function() {
}
if (includePre) html.push("</pre>");
return html.join("");
+ },
+ cleanUp: function() {
+ this.parent = null;
+ if (this.marked)
+ for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
}
};
// Utility used by replace and split above
@@ -1874,6 +2309,191 @@ var CodeMirror = (function() {
}
}
+ // Data structure that holds the sequence of lines.
+ function LeafChunk(lines) {
+ this.lines = lines;
+ this.parent = null;
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
+ lines[i].parent = this;
+ height += lines[i].height;
+ }
+ this.height = height;
+ }
+ LeafChunk.prototype = {
+ chunkSize: function() { return this.lines.length; },
+ remove: function(at, n) {
+ for (var i = at, e = at + n; i < e; ++i) {
+ var line = this.lines[i];
+ line.cleanUp();
+ this.height -= line.height;
+ }
+ this.lines.splice(at, n);
+ },
+ collapse: function(lines) {
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
+ },
+ insertHeight: function(at, lines, height) {
+ this.height += height;
+ this.lines.splice.apply(this.lines, [at, 0].concat(lines));
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
+ },
+ iterN: function(at, n, op) {
+ for (var e = at + n; at < e; ++at)
+ if (op(this.lines[at])) return true;
+ }
+ };
+ function BranchChunk(children) {
+ this.children = children;
+ var size = 0, height = 0;
+ for (var i = 0, e = children.length; i < e; ++i) {
+ var ch = children[i];
+ size += ch.chunkSize(); height += ch.height;
+ ch.parent = this;
+ }
+ this.size = size;
+ this.height = height;
+ this.parent = null;
+ }
+ BranchChunk.prototype = {
+ chunkSize: function() { return this.size; },
+ remove: function(at, n) {
+ this.size -= n;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
+ child.remove(at, rm);
+ this.height -= oldHeight - child.height;
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
+ if ((n -= rm) == 0) break;
+ at = 0;
+ } else at -= sz;
+ }
+ if (this.size - n < 25) {
+ var lines = [];
+ this.collapse(lines);
+ this.children = [new LeafChunk(lines)];
+ }
+ },
+ collapse: function(lines) {
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
+ },
+ insert: function(at, lines) {
+ var height = 0;
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
+ this.insertHeight(at, lines, height);
+ },
+ insertHeight: function(at, lines, height) {
+ this.size += lines.length;
+ this.height += height;
+ for (var i = 0, e = this.children.length; i < e; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at <= sz) {
+ child.insertHeight(at, lines, height);
+ if (child.lines && child.lines.length > 50) {
+ while (child.lines.length > 50) {
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
+ var newleaf = new LeafChunk(spilled);
+ child.height -= newleaf.height;
+ this.children.splice(i + 1, 0, newleaf);
+ newleaf.parent = this;
+ }
+ this.maybeSpill();
+ }
+ break;
+ }
+ at -= sz;
+ }
+ },
+ maybeSpill: function() {
+ if (this.children.length <= 10) return;
+ var me = this;
+ do {
+ var spilled = me.children.splice(me.children.length - 5, 5);
+ var sibling = new BranchChunk(spilled);
+ if (!me.parent) { // Become the parent node
+ var copy = new BranchChunk(me.children);
+ copy.parent = me;
+ me.children = [copy, sibling];
+ me = copy;
+ } else {
+ me.size -= sibling.size;
+ me.height -= sibling.height;
+ var myIndex = indexOf(me.parent.children, me);
+ me.parent.children.splice(myIndex + 1, 0, sibling);
+ }
+ sibling.parent = me.parent;
+ } while (me.children.length > 10);
+ me.parent.maybeSpill();
+ },
+ iter: function(from, to, op) { this.iterN(from, to - from, op); },
+ iterN: function(at, n, op) {
+ for (var i = 0, e = this.children.length; i < e; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var used = Math.min(n, sz - at);
+ if (child.iterN(at, used, op)) return true;
+ if ((n -= used) == 0) break;
+ at = 0;
+ } else at -= sz;
+ }
+ }
+ };
+
+ function getLineAt(chunk, n) {
+ for (;;) {
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ var child = chunk.children[i], sz = child.chunkSize();
+ if (n < sz) { chunk = child; break; }
+ n -= sz;
+ }
+ if (chunk.lines) return chunk.lines[n];
+ }
+ }
+ function lineNo(line) {
+ if (line.parent == null) return null;
+ var cur = line.parent, no = indexOf(cur.lines, line);
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
+ for (var i = 0, e = chunk.children.length; ; ++i) {
+ if (chunk.children[i] == cur) break;
+ no += chunk.children[i].chunkSize();
+ }
+ }
+ return no;
+ }
+ function lineAtHeight(chunk, h) {
+ var n = 0;
+ outer: do {
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ var child = chunk.children[i], ch = child.height;
+ if (h < ch) { chunk = child; continue outer; }
+ h -= ch;
+ n += child.chunkSize();
+ }
+ return n;
+ } while (!chunk.lines);
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
+ var line = chunk.lines[i], lh = line.height;
+ if (h < lh) break;
+ h -= lh;
+ }
+ return n + i;
+ }
+ function heightAtLine(chunk, n) {
+ var h = 0;
+ outer: do {
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ var child = chunk.children[i], sz = child.chunkSize();
+ if (n < sz) { chunk = child; continue outer; }
+ n -= sz;
+ h += child.height;
+ }
+ return h;
+ } while (!chunk.lines);
+ for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
+ return h;
+ }
+
// The history object 'chunks' changes that are made close together
// and at almost the same time into bigger undoable units.
function History() {
@@ -1930,16 +2550,6 @@ var CodeMirror = (function() {
else if (e.button & 2) return 3;
else if (e.button & 4) return 2;
}
- function e_pageX(e) {
- if (e.pageX != null) return e.pageX;
- var doc = e_target(e).ownerDocument;
- return e.clientX + doc.body.scrollLeft + doc.documentElement.scrollLeft;
- }
- function e_pageY(e) {
- if (e.pageY != null) return e.pageY;
- var doc = e_target(e).ownerDocument;
- return e.clientY + doc.body.scrollTop + doc.documentElement.scrollTop;
- }
// Event handler registration. If disconnect is true, it'll return a
// function that unregisters the handler.
@@ -1958,16 +2568,18 @@ var CodeMirror = (function() {
function Delayed() {this.id = null;}
Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
- // Some IE versions don't preserve whitespace when setting the
- // innerHTML of a PRE tag.
- var badInnerHTML = (function() {
- var pre = document.createElement("pre");
- pre.innerHTML = " "; return !pre.innerHTML;
- })();
+ // Detect drag-and-drop
+ var dragAndDrop = function() {
+ // IE8 has ondragstart and ondrop properties, but doesn't seem to
+ // actually support ondragstart the way it's supposed to work.
+ if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false;
+ var div = document.createElement('div');
+ return "draggable" in div;
+ }();
var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
var ie = /MSIE \d/.test(navigator.userAgent);
- var safari = /Apple Computer/.test(navigator.vendor);
+ var webkit = /WebKit\//.test(navigator.userAgent);
var lineSep = "\n";
// Feature-detect whether newlines in textareas are converted to \r\n
@@ -1979,6 +2591,7 @@ var CodeMirror = (function() {
var tabSize = 8;
var mac = /Mac/.test(navigator.platform);
+ var win = /Win/.test(navigator.platform);
var movementKeys = {};
for (var i = 35; i <= 40; ++i)
movementKeys[i] = movementKeys["c" + i] = true;
@@ -2001,21 +2614,44 @@ var CodeMirror = (function() {
if (elt.currentStyle) return elt.currentStyle;
return window.getComputedStyle(elt, null);
}
+
// Find the position of an element by following the offsetParent chain.
// If screen==true, it returns screen (rather than page) coordinates.
function eltOffset(node, screen) {
- var doc = node.ownerDocument.body;
- var x = 0, y = 0, skipDoc = false;
+ var bod = node.ownerDocument.body;
+ var x = 0, y = 0, skipBody = false;
for (var n = node; n; n = n.offsetParent) {
- x += n.offsetLeft; y += n.offsetTop;
+ var ol = n.offsetLeft, ot = n.offsetTop;
+ // Firefox reports weird inverted offsets when the body has a border.
+ if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
+ else { x += ol, y += ot; }
if (screen && computedStyle(n).position == "fixed")
- skipDoc = true;
+ skipBody = true;
}
- var e = screen && !skipDoc ? null : doc;
+ var e = screen && !skipBody ? null : bod;
for (var n = node.parentNode; n != e; n = n.parentNode)
if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
return {left: x, top: y};
}
+ // Use the faster and saner getBoundingClientRect method when possible.
+ if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
+ // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
+ // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
+ try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
+ catch(e) { box = {top: 0, left: 0}; }
+ if (!screen) {
+ // Get the toplevel scroll, working around browser differences.
+ if (window.pageYOffset == null) {
+ var t = document.documentElement || document.body.parentNode;
+ if (t.scrollTop == null) t = document.body;
+ box.top += t.scrollTop; box.left += t.scrollLeft;
+ } else {
+ box.top += window.pageYOffset; box.left += window.pageXOffset;
+ }
+ }
+ return box;
+ };
+
// Get a node's text content.
function eltText(node) {
return node.textContent || node.innerText || node.nodeValue || "";
@@ -2026,11 +2662,17 @@ var CodeMirror = (function() {
function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
function copyPos(x) {return {line: x.line, ch: x.ch};}
- var escapeElement = document.createElement("div");
+ var escapeElement = document.createElement("pre");
function htmlEscape(str) {
- escapeElement.innerText = escapeElement.textContent = str;
+ if (badTextContent) {
+ escapeElement.innerHTML = "";
+ escapeElement.appendChild(document.createTextNode(str));
+ } else {
+ escapeElement.textContent = str;
+ }
return escapeElement.innerHTML;
}
+ var badTextContent = htmlEscape("\t") != "\t";
CodeMirror.htmlEscape = htmlEscape;
// Used to position the cursor after an undo/redo by finding the
@@ -2073,7 +2715,7 @@ var CodeMirror = (function() {
try {return {start: te.selectionStart, end: te.selectionEnd};}
catch(e) {return null;}
};
- if (safari)
+ if (webkit)
// On Safari, selection set with setSelectionRange are in a sort
// of limbo wrt their anchor. If you press shift-left in them,
// the anchor is put at the end, and the selection expanded to
@@ -2140,5 +2782,4 @@ var CodeMirror = (function() {
CodeMirror.defineMIME("text/plain", "null");
return CodeMirror;
-})()
-; \ No newline at end of file
+})();