' + // Set to the height of the text, causes scrolling
- '' + // To measure line/char size
- 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
' +
- '' + // Moved around its parent to cover visible view
- '
' +
- '
' + // Wraps and hides input textarea
- '
' +
- // Provides positioning relative to (visible) text origin
- '
' +
- '
' + // Absolutely positioned blinky cursor
- '
'; // This DIV contains the actual code
+ '' + // Wraps and hides input textarea
+ '
' +
+ '';
if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
// I've never seen more elegant code in my life.
- var code = wrapper.firstChild, measure = code.firstChild, mover = measure.nextSibling,
+ 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,
- inputDiv = gutter.nextSibling, input = inputDiv.firstChild,
- lineSpace = inputDiv.nextSibling.firstChild, cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
+ lineSpace = gutter.nextSibling.firstChild,
+ cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling;
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;
@@ -46,7 +59,7 @@ var CodeMirror = (function() {
// (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;
+ var mode, lines = [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
@@ -56,10 +69,10 @@ 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;
+ 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;
// editing will hold an object describing the things we put in the
@@ -67,35 +80,46 @@ var CodeMirror = (function() {
// bracketHighlighted is used to remember that a backet has been
// marked.
var editing, bracketHighlighted;
+ // Tracks the maximum line length so that the horizontal scrollbar
+ // can be kept static when scrolling.
+ var maxLine = "", maxWidth;
- // Initialize the content. Somewhat hacky (delayed prepareInput)
- // to work around browser issues.
+ // Initialize the content.
operation(function(){setValue(options.value || ""); updateInput = false;})();
- setTimeout(prepareInput, 20);
+ var history = new History();
// Register our event handlers.
- connect(wrapper, "mousedown", operation(onMouseDown));
+ connect(scroller, "mousedown", operation(onMouseDown));
+ connect(scroller, "dblclick", operation(onDoubleClick));
+ connect(lineSpace, "dragstart", onDragStart);
// 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(wrapper, "contextmenu", operation(onContextMenu));
- connect(code, "dblclick", operation(onDblClick));
- connect(wrapper, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);});
+ if (!gecko) connect(scroller, "contextmenu", onContextMenu);
+ 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);
connect(input, "blur", onBlur);
- connect(wrapper, "dragenter", function(e){e.stop();});
- connect(wrapper, "dragover", function(e){e.stop();});
- connect(wrapper, "drop", operation(onDrop));
- connect(wrapper, "paste", function(){input.focus(); fastPoll();});
+ connect(scroller, "dragenter", e_stop);
+ connect(scroller, "dragover", e_stop);
+ connect(scroller, "drop", operation(onDrop));
+ connect(scroller, "paste", function(){focusInput(); fastPoll();});
connect(input, "paste", function(){fastPoll();});
connect(input, "cut", function(){fastPoll();});
- if (document.activeElement == input) onFocus();
+ // 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;}
@@ -104,27 +128,37 @@ var CodeMirror = (function() {
// 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(){input.focus(); onFocus(); fastPoll();},
+ focus: function(){focusInput(); onFocus(); fastPoll();},
setOption: function(option, value) {
options[option] = value;
- if (option == "lineNumbers" || option == "gutter") gutterChanged();
+ if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber")
+ operation(gutterChanged)();
else 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);
},
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) {
pos = clipPos(pos);
return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch);
},
+ getStateAfter: function(line) {
+ line = clipLine(line == null ? lines.length - 1: line);
+ return getStateBefore(line + 1);
+ },
cursorCoords: function(start){
if (start == null) start = sel.inverted;
return pageCoords(start ? sel.from : sel.to);
@@ -132,22 +166,41 @@ var CodeMirror = (function() {
charCoords: function(pos){return pageCoords(clipPos(pos));},
coordsChar: function(coords) {
var off = eltOffset(lineSpace);
- var line = Math.min(showingTo - 1, showingFrom + Math.floor(coords.y / lineHeight()));
- return clipPos({line: line, ch: charFromX(clipLine(line), coords.x)});
+ 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)});
},
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),
+ setMarker: operation(addGutterMarker),
+ clearMarker: operation(removeGutterMarker),
setLineClass: operation(setLineClass),
lineInfo: lineInfo,
- addWidget: function(pos, node, scroll) {
- var pos = localCoords(clipPos(pos), true);
- node.style.top = (showingFrom * lineHeight() + pos.yBot + paddingTop()) + "px";
- node.style.left = (pos.x + paddingLeft()) + "px";
+ 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);
+ if (vert == "over") top = pos.y;
+ else if (vert == "near") {
+ var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()),
+ hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
+ if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
+ top = pos.y - node.offsetHeight;
+ if (left + node.offsetWidth > hspace)
+ left = hspace - node.offsetWidth;
+ }
+ node.style.top = (top + paddingTop()) + "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(pos.x, pos.yBot, pos.x + node.offsetWidth, pos.yBot + node.offsetHeight);
+ scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
},
lineCount: function() {return lines.length;},
@@ -171,18 +224,30 @@ var CodeMirror = (function() {
replaceRange: operation(replaceRange),
getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
+ coordsFromIndex: function(index) {
+ var total = lines.length, pos = 0, line, ch, len;
+
+ for (line = 0; line < total; line++) {
+ len = lines[line].text.length + 1;
+ if (pos + len > index) { ch = index - pos; break; }
+ pos += len;
+ }
+ return clipPos({line: line, ch: ch});
+ },
+
operation: function(f){return operation(f)();},
refresh: function(){updateDisplay(true);},
getInputField: function(){return input;},
- getWrapperElement: function(){return wrapper;}
+ getWrapperElement: function(){return wrapper;},
+ getScrollerElement: function(){return scroller;},
+ getGutterElement: function(){return gutter;}
};
function setValue(code) {
- history = null;
var top = {line: 0, ch: 0};
updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length},
splitLines(code), top, top);
- history = new History();
+ updateInput = true;
}
function getValue(code) {
var text = [];
@@ -192,38 +257,70 @@ var CodeMirror = (function() {
}
function onMouseDown(e) {
+ // 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;
+
// First, see if this is a click in the gutter
- for (var n = e.target(); n != wrapper; n = n.parentNode)
+ 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);
- return e.stop();
+ options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
+ return e_preventDefault(e);
}
- if (gecko && e.button() == 3) onContextMenu(e);
- if (e.button() != 1) return;
+ var start = posFromMouse(e);
+
+ switch (e_button(e)) {
+ case 3:
+ if (gecko && !mac) onContextMenu(e);
+ return;
+ case 2:
+ if (start) setCursor(start.line, start.ch, true);
+ return;
+ }
// For button 1, if it was clicked inside the editor
// (posFromMouse returning non-null), we have to adjust the
// selection.
- var start = posFromMouse(e), last = start, going;
- if (!start) {if (e.target() == wrapper) e.stop(); return;}
- setCursor(start.line, start.ch, false);
+ if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
if (!focused) onFocus();
- e.stop();
- // And then we have to see if it's a drag event, in which case
- // the dragged-over text must be selected.
- function end() {
- input.focus();
- updateInput = true;
- move(); up();
+
+ var now = +new Date;
+ if (lastDoubleClick > now - 400) {
+ e_preventDefault(e);
+ return selectLine(start.line);
+ } else if (lastClick > now - 400) {
+ lastDoubleClick = now;
+ e_preventDefault(e);
+ return selectWordAt(start);
+ } else { lastClick = now; }
+
+ var last = start, going;
+ if (dragAndDrop && !posEq(sel.from, sel.to) &&
+ !posLess(start, sel.from) && !posLess(sel.to, start)) {
+ // Let the drag handler handle this.
+ var up = connect(targetDocument, "mouseup", operation(function(e2) {
+ 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)) {
if (!focused) onFocus();
last = cur;
- setSelection(start, cur);
+ setSelectionUser(start, cur);
updateInput = false;
var visible = visibleLines();
if (cur.line >= visible.to || cur.line < visible.from)
@@ -231,68 +328,95 @@ var CodeMirror = (function() {
}
}
- var move = connect(document, "mousemove", operation(function(e) {
+ var move = connect(targetDocument, "mousemove", operation(function(e) {
clearTimeout(going);
- e.stop();
+ e_preventDefault(e);
extend(e);
}), true);
- var up = connect(document, "mouseup", operation(function(e) {
+ var up = connect(targetDocument, "mouseup", operation(function(e) {
clearTimeout(going);
var cur = posFromMouse(e);
- if (cur) setSelection(start, cur);
- e.stop();
- end();
+ if (cur) setSelectionUser(start, cur);
+ e_preventDefault(e);
+ focusInput();
+ updateInput = true;
+ move(); up();
}), true);
}
- function onDblClick(e) {
- var pos = posFromMouse(e);
- if (!pos) return;
- selectWordAt(pos);
- e.stop();
+ function onDoubleClick(e) {
+ var start = posFromMouse(e);
+ if (!start) return;
+ lastDoubleClick = +new Date;
+ e_preventDefault(e);
+ selectWordAt(start);
}
function onDrop(e) {
- var pos = posFromMouse(e, true), files = e.e.dataTransfer.files;
+ e.preventDefault();
+ var pos = posFromMouse(e, true), files = e.dataTransfer.files;
if (!pos || options.readOnly) return;
if (files && files.length && window.FileReader && window.File) {
- var n = files.length, text = Array(n), read = 0;
- for (var i = 0; i < n; ++i) loadFile(files[i], i);
function loadFile(file, i) {
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);
+ var end = replaceRange(text.join(""), pos, pos);
+ setSelectionUser(pos, end);
+ }
};
reader.readAsText(file);
}
+ var n = files.length, text = Array(n), read = 0;
+ for (var i = 0; i < n; ++i) loadFile(files[i], i);
}
else {
try {
- var text = e.e.dataTransfer.getData("Text");
- if (text) replaceRange(text, pos, pos);
+ var text = e.dataTransfer.getData("Text");
+ 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();
- var code = e.e.keyCode;
+ var code = e.keyCode;
+ // IE does strange things with escape.
+ if (ie && code == 27) { e.returnValue = false; }
// Tries to detect ctrl on non-mac, cmd on mac.
- var mod = (mac ? e.e.metaKey : e.e.ctrlKey) && !e.e.altKey, anyMod = e.e.ctrlKey || e.e.altKey || e.e.metaKey;
- if (code == 16 || e.e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
+ var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey;
+ if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
else shiftSelecting = null;
// First give onKeyEvent option a chance to handle this.
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
- if (code == 33 || code == 34) {scrollPage(code == 34); return e.stop();} // page up/down
- if (mod && (code == 36 || code == 35)) {scrollEnd(code == 36); return e.stop();} // ctrl-home/end
- if (mod && code == 65) {selectAll(); return e.stop();} // ctrl-a
+ if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down
+ if (mod && ((code == 36 || code == 35) || // ctrl-home/end
+ mac && (code == 38 || code == 40))) { // cmd-up/down
+ scrollEnd(code == 36 || code == 38); return e_preventDefault(e);
+ }
+ if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a
if (!options.readOnly) {
if (!anyMod && code == 13) {return;} // enter
- if (!anyMod && code == 9 && handleTab(e.e.shiftKey)) return e.stop(); // tab
- if (mod && code == 90) {undo(); return e.stop();} // ctrl-z
- if (mod && ((e.e.shiftKey && code == 90) || code == 89)) {redo(); return e.stop();} // ctrl-shift-z, ctrl-y
+ if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab
+ 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
@@ -300,51 +424,60 @@ 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);
}
function onKeyUp(e) {
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
if (reducedSelection) {
reducedSelection = null;
updateInput = true;
}
- if (e.e.keyCode == 16) shiftSelecting = null;
+ if (e.keyCode == 16) shiftSelecting = null;
}
function onKeyPress(e) {
- if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e.e))) return;
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
if (options.electricChars && mode.electricChars) {
- var ch = String.fromCharCode(e.e.charCode == null ? e.e.keyCode : e.e.charCode);
+ var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);
if (mode.electricChars.indexOf(ch) > -1)
setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50);
}
- var code = e.e.keyCode;
+ var code = e.keyCode;
// Re-stop tab and enter. Necessary on some browsers.
- if (code == 13) {handleEnter(); e.stop();}
- else if (code == 9 && options.tabMode != "default") e.stop();
+ if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);}
+ else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e);
else fastPoll(curKeyId);
}
function onFocus() {
- if (!focused && options.onFocus) options.onFocus(instance);
- focused = true;
+ if (options.readOnly == "nocursor") return;
+ if (!focused) {
+ if (options.onFocus) options.onFocus(instance);
+ focused = true;
+ if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
+ wrapper.className += " CodeMirror-focused";
+ if (!leaveInputAlone) prepareInput();
+ }
slowPoll();
- if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
- wrapper.className += " CodeMirror-focused";
restartBlink();
}
function onBlur() {
- if (focused && options.onBlur) options.onBlur(instance);
+ if (focused) {
+ if (options.onBlur) options.onBlur(instance);
+ focused = false;
+ wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
+ }
clearInterval(blinker);
- shiftSelecting = null;
- focused = false;
- wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
+ setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
}
// Replace the range from from to to by the strings in newText.
@@ -367,12 +500,18 @@ var CodeMirror = (function() {
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);
+ updateInput = true;
}
}
function undo() {unredoHelper(history.done, history.undone);}
function redo() {unredoHelper(history.undone, history.done);}
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;}
+ }
+
var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line];
// First adjust the line structure, taking some care to leave highlighting intact.
if (firstLine == lastLine) {
@@ -381,24 +520,46 @@ var CodeMirror = (function() {
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]));
+ firstLine.replace(from.ch, null, newText[0]);
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
+ spliceargs.push(Line.inheritMarks(newText[i], firstLine));
spliceargs.push(lastLine);
lines.splice.apply(lines, spliceargs);
}
}
else if (newText.length == 1) {
- firstLine.replace(from.ch, firstLine.text.length, newText[0] + lastLine.text.slice(to.ch));
+ firstLine.replace(from.ch, null, newText[0]);
+ lastLine.replace(null, to.ch, "");
+ firstLine.append(lastLine);
lines.splice(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]));
+ firstLine.replace(from.ch, null, newText[0]);
+ lastLine.replace(null, to.ch, newText[newText.length-1]);
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
+ spliceargs.push(Line.inheritMarks(newText[i], firstLine));
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;
+ if (l.length > maxLineLength) {
+ maxLineLength = l.length; maxLine = l;
+ }
+ }
+ }
+
// Add these lines to the work array, so that they will be
// highlighted. Adjust work lines if lines were added/removed.
var newWork = [], lendiff = newText.length - nlines - 1;
@@ -407,12 +568,17 @@ var CodeMirror = (function() {
if (task < from.line) newWork.push(task);
else if (task > to.line) newWork.push(task + lendiff);
}
- if (newText.length) newWork.push(from.line);
+ if (newText.length < 5) {
+ highlightLines(from.line, from.line + newText.length);
+ newWork.push(from.line + newText.length);
+ } else {
+ newWork.push(from.line);
+ }
work = newWork;
startWorker(100);
// Remember that these lines changed, for updating the display
changes.push({from: from.line, to: to.line + 1, diff: lendiff});
- textChanged = true;
+ textChanged = {from: from, to: to, text: newText};
// Update the selection
function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
@@ -483,7 +649,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();
@@ -495,13 +664,12 @@ var CodeMirror = (function() {
// to the data in the editing variable, and updates the editor
// content or cursor if something changed.
function readInput() {
+ if (leaveInputAlone || !focused) return;
var changed = false, text = input.value, sr = selRange(input);
if (!sr) return false;
var changed = editing.text != text, rs = reducedSelection;
var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end);
- if (reducedSelection && !moved && sel.from.line == 0 && sel.from.ch == 0)
- reducedSelection = null;
- else if (!moved) return false;
+ if (!moved && !rs) return false;
if (changed) {
shiftSelecting = reducedSelection = null;
if (options.readOnly) {updateInput = true; return "changed";}
@@ -524,13 +692,10 @@ var CodeMirror = (function() {
// so that you can, for example, press shift-up at the start of
// your selection and have the right thing happen.
if (rs) {
- from = sr.start == rs.anchor ? to : from;
- to = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
- if (!posLess(from, to)) {
- reducedSelection = null;
- sel.inverted = false;
- var tmp = from; from = to; to = tmp;
- }
+ var head = sr.start == rs.anchor ? to : from;
+ var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to;
+ if (sel.inverted = posLess(head, tail)) { from = head; to = tail; }
+ else { reducedSelection = null; from = tail; to = head; }
}
// In some cases (cursor on same line as before), we don't have
@@ -550,8 +715,8 @@ var CodeMirror = (function() {
var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length;
for (;;) {
c = editing.text.charAt(edend);
+ if (text.charAt(end) != c) {++end; ++edend; break;}
if (c == "\n") endline--;
- if (text.charAt(end) != c) {++end; ++edend; break;}
if (edend <= start || end <= start) break;
--end; --edend;
}
@@ -580,22 +745,36 @@ var CodeMirror = (function() {
editing = {text: text, from: from, to: to, start: startch, end: endch};
setSelRange(input, startch, reducedSelection ? startch : endch);
}
+ function focusInput() {
+ 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);
}
function scrollIntoView(x1, y1, x2, y2) {
- var pl = paddingLeft(), pt = paddingTop();
+ var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight();
y1 += pt; y2 += pt; x1 += pl; x2 += pl;
- var screen = wrapper.clientHeight, screentop = wrapper.scrollTop, scrolled = false, result = true;
- if (y1 < screentop) {wrapper.scrollTop = Math.max(0, y1 - 10); scrolled = true;}
- else if (y2 > screentop + screen) {wrapper.scrollTop = y2 + 10 - screen; scrolled = true;}
+ 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 = wrapper.clientWidth, screenleft = wrapper.scrollLeft;
- if (x1 < screenleft) {wrapper.scrollLeft = Math.max(0, x1 - 10); scrolled = true;}
+ var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
+ var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
+ if (x1 < screenleft + gutterw) {
+ if (x1 < 50) x1 = 0;
+ scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
+ scrolled = true;
+ }
else if (x2 > screenw + screenleft) {
- wrapper.scrollLeft = x2 + 10 - screenw;
+ scroller.scrollLeft = x2 + 10 - screenw;
scrolled = true;
if (x2 > code.clientWidth) result = false;
}
@@ -604,15 +783,15 @@ var CodeMirror = (function() {
}
function visibleLines() {
- var lh = lineHeight(), top = wrapper.scrollTop - paddingTop();
+ 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 + wrapper.clientHeight) / lh))};
+ to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))};
}
// 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 (!wrapper.clientWidth) {
+ if (!scroller.clientWidth) {
showingFrom = showingTo = 0;
return;
}
@@ -629,7 +808,7 @@ var CodeMirror = (function() {
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)});
@@ -659,6 +838,7 @@ var CodeMirror = (function() {
if (domPos != domEnd || pos != to) {
changedLines += Math.abs(to - pos);
updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos});
+ if (to - pos != domEnd - domPos) gutterDirty = true;
}
if (!updates.length) return;
@@ -674,13 +854,23 @@ var CodeMirror = (function() {
// 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 != wrapper.clientHeight;
+ var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight;
showingFrom = from; showingTo = to;
mover.style.top = (from * lineHeight()) + "px";
if (different) {
- lastHeight = wrapper.clientHeight;
+ lastHeight = scroller.clientHeight;
code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px";
- updateGutter();
+ }
+ if (different || gutterDirty) 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 = "";
}
// Since this is all rather error prone, it is honoured with the
@@ -712,7 +902,7 @@ var CodeMirror = (function() {
// there .innerHTML on PRE nodes is dumb, and discards
// whitespace.
var sfrom = sel.from.line, sto = sel.to.line, off = 0,
- scratch = badInnerHTML && document.createElement("div");
+ 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;
@@ -722,7 +912,7 @@ var CodeMirror = (function() {
lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
else if (extra) {
for (var j = Math.max(0, extra); j > 0; --j)
- lineDiv.insertBefore(document.createElement("pre"), nodeAfter);
+ lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter);
for (var j = Math.max(0, -extra); j > 0; --j)
lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild);
}
@@ -753,10 +943,10 @@ var CodeMirror = (function() {
function updateGutter() {
if (!options.gutter && !options.lineNumbers) return;
- var hText = mover.offsetHeight, hEditor = wrapper.clientHeight;
+ var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
var html = [];
- for (var i = showingFrom; i < showingTo; ++i) {
+ 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)
@@ -769,37 +959,43 @@ var CodeMirror = (function() {
gutterText.innerHTML = html.join("");
var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = "";
while (val.length + pad.length < minwidth) pad += "\u00a0";
- if (pad) firstNode.insertBefore(document.createTextNode(pad), firstNode.firstChild);
+ 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;
- var x = charX(head.line, head.ch) + "px", y = (head.line - showingFrom) * lineHeight() + "px";
- inputDiv.style.top = y; inputDiv.style.left = x;
+ var head = sel.inverted ? sel.from : sel.to, lh = lineHeight();
+ var x = charX(head.line, head.ch);
+ var top = head.line * lh - scroller.scrollTop;
+ inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px";
+ inputDiv.style.left = (x - scroller.scrollLeft) + "px";
if (posEq(sel.from, sel.to)) {
- cursor.style.top = y; cursor.style.left = x;
+ cursor.style.top = (head.line - showingFrom) * lh + "px";
+ cursor.style.left = x + "px";
cursor.style.display = "";
}
else cursor.style.display = "none";
}
+ function setSelectionUser(from, to) {
+ var sh = shiftSelecting && clipPos(shiftSelecting);
+ if (sh) {
+ if (posLess(sh, from)) from = sh;
+ else if (posLess(to, sh)) to = sh;
+ }
+ setSelection(from, to);
+ }
// Update the selection. Last two args are only used by
// updateLines, since they have to be expressed in the line
// numbers before the update.
function setSelection(from, to, oldFrom, oldTo) {
if (posEq(sel.from, from) && posEq(sel.to, to)) return;
- var sh = shiftSelecting && clipPos(shiftSelecting);
if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
- if (sh) {
- if (posLess(sh, from)) from = sh;
- else if (posLess(to, sh)) to = sh;
- }
- var startEq = posEq(sel.to, to), endEq = posEq(sel.from, from);
if (posEq(from, to)) sel.inverted = false;
- else if (startEq && !endEq) sel.inverted = true;
- else if (endEq && !startEq) sel.inverted = false;
+ else if (posEq(from, sel.to)) sel.inverted = false;
+ else if (posEq(to, sel.from)) sel.inverted = true;
// Some ugly logic used to only mark the lines that actually did
// see a change in selection as changed, rather than the whole
@@ -829,9 +1025,9 @@ var CodeMirror = (function() {
sel.from = from; sel.to = to;
selectionChanged = true;
}
- function setCursor(line, ch) {
+ function setCursor(line, ch, user) {
var pos = clipPos({line: line, ch: ch || 0});
- setSelection(pos, pos);
+ (user ? setSelectionUser : setSelection)(pos, pos);
}
function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));}
@@ -845,11 +1041,12 @@ var CodeMirror = (function() {
}
function scrollPage(down) {
- var linesPerPage = Math.floor(wrapper.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to;
- setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch);
+ 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);
}
function scrollEnd(top) {
- setCursor(top ? 0 : lines.length - 1);
+ var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length};
+ setSelectionUser(pos, pos);
}
function selectAll() {
var endLine = lines.length - 1;
@@ -859,8 +1056,11 @@ var CodeMirror = (function() {
var line = lines[pos.line].text;
var start = pos.ch, end = pos.ch;
while (start > 0 && /\w/.test(line.charAt(start - 1))) --start;
- while (end < line.length - 1 && /\w/.test(line.charAt(end))) ++end;
- setSelection({line: pos.line, ch: start}, {line: pos.line, ch: end});
+ 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});
}
function handleEnter() {
replaceSelection("\n", "end");
@@ -868,12 +1068,17 @@ var CodeMirror = (function() {
indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart");
}
function handleTab(shift) {
+ function indentSelected(mode) {
+ if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
+ for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
+ }
shiftSelecting = null;
switch (options.tabMode) {
case "default":
return false;
case "indent":
- for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, "smart");
+ indentSelected("smart");
break;
case "classic":
if (posEq(sel.from, sel.to)) {
@@ -882,11 +1087,15 @@ var CodeMirror = (function() {
break;
}
case "shift":
- for (var i = sel.from.line, e = sel.to.line; i <= e; ++i) indentLine(i, shift ? "subtract" : "add");
+ indentSelected(shift ? "subtract" : "add");
break;
}
return true;
}
+ function smartHome() {
+ var firstNonWS = Math.max(0, lines[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") {
@@ -924,21 +1133,20 @@ var CodeMirror = (function() {
for (var i = 0, l = lines.length; i < l; ++i)
lines[i].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 markText(from, to, className) {
from = clipPos(from); to = clipPos(to);
- var accum = [];
+ var set = [];
function add(line, from, to, className) {
- var line = lines[line], mark = line.addMark(from, to, className);
- mark.line = line;
- accum.push(mark);
+ mark = lines[line].addMark(from, to, className, set);
}
if (from.line == to.line) add(from.line, from.ch, to.ch, className);
else {
@@ -948,30 +1156,51 @@ var CodeMirror = (function() {
add(to.line, 0, 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;
+ return new TextMarker(set);
+ }
+
+ function TextMarker(set) { this.set = set; }
+ TextMarker.prototype.clear = operation(function() {
+ for (var i = 0, e = this.set.length; i < e; ++i) {
+ var mk = this.set[i].marked;
+ 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: lines.length});
+ });
+ 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 = indexOf(lines, line);
+ if (found > -1) {
+ if (mark.from != null) from = {line: found, ch: mark.from};
+ if (mark.to != null) to = {line: found, ch: mark.to};
+ }
+ }
}
}
- if (start != null) changes.push({from: start, to: end + 1});
- };
- }
+ }
+ return {from: from, to: to};
+ };
function addGutterMarker(line, text, className) {
if (typeof line == "number") line = lines[clipLine(line)];
line.gutterMarker = {text: text, style: className};
- updateGutter();
+ gutterDirty = true;
return line;
}
function removeGutterMarker(line) {
if (typeof line == "number") line = lines[clipLine(line)];
line.gutterMarker = null;
- updateGutter();
+ gutterDirty = true;
}
function setLineClass(line, className) {
if (typeof line == "number") {
@@ -982,8 +1211,10 @@ var CodeMirror = (function() {
var no = indexOf(lines, line);
if (no == -1) return null;
}
- line.className = className;
- changes.push({from: no, to: no + 1});
+ if (line.className != className) {
+ line.className = className;
+ changes.push({from: no, to: no + 1});
+ }
return line;
}
@@ -1001,35 +1232,44 @@ var CodeMirror = (function() {
return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style};
}
+ function stringWidth(str) {
+ measure.innerHTML = "x
";
+ measure.firstChild.firstChild.firstChild.nodeValue = str;
+ return measure.firstChild.firstChild.offsetWidth || 10;
+ }
// These are used to go from pixel positions to character
- // positions, taking tabs into account.
+ // positions, taking varying character widths into account.
function charX(line, pos) {
- var text = lines[line].text, span = measure.firstChild;
- if (text.lastIndexOf("\t", pos) == -1) return pos * charWidth();
- var old = span.firstChild.nodeValue;
- try {
- span.firstChild.nodeValue = text.slice(0, pos);
- return span.offsetWidth;
- } finally {span.firstChild.nodeValue = old;}
+ if (pos == 0) return 0;
+ measure.innerHTML = "" + lines[line].getHTML(null, null, false, pos) + "
";
+ return measure.firstChild.firstChild.offsetWidth;
}
function charFromX(line, x) {
- var text = lines[line].text, cw = charWidth();
if (x <= 0) return 0;
- if (text.indexOf("\t") == -1) return Math.min(text.length, Math.round(x / cw));
- var mspan = measure.firstChild, mtext = mspan.firstChild, old = mtext.nodeValue;
- try {
- mtext.nodeValue = text;
- var from = 0, fromX = 0, to = text.length, toX = mspan.offsetWidth;
- if (x > toX) return to;
- for (;;) {
- if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
- var middle = Math.ceil((from + to) / 2);
- mtext.nodeValue = text.slice(0, middle);
- var curX = mspan.offsetWidth;
- if (curX > x) {to = middle; toX = curX;}
- else {from = middle; fromX = curX;}
- }
- } finally {mtext.nodeValue = old;}
+ var lineObj = lines[line], text = lineObj.text;
+ function getX(len) {
+ measure.innerHTML = "" + lineObj.getHTML(null, null, false, len) + "
";
+ return measure.firstChild.firstChild.offsetWidth;
+ }
+ var from = 0, fromX = 0, to = text.length, toX;
+ // Guess a suitable upper bound for our search.
+ var estimated = Math.min(to, Math.ceil(x / stringWidth("x")));
+ for (;;) {
+ var estX = getX(estimated);
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
+ else {toX = estX; to = estimated; break;}
+ }
+ if (x > toX) return to;
+ // Try to guess a suitable lower bound as well.
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
+ if (estX < x) {from = estimated; fromX = estX;}
+ // Do a binary search between these bounds.
+ for (;;) {
+ if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
+ var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
+ if (middleX > x) {to = middle; toX = middleX;}
+ else {from = middle; fromX = middleX;}
+ }
}
function localCoords(pos, inLineWrap) {
@@ -1043,45 +1283,61 @@ var CodeMirror = (function() {
function lineHeight() {
var nlines = lineDiv.childNodes.length;
- if (nlines) return lineDiv.offsetHeight / nlines;
- else return measure.firstChild.offsetHeight || 1;
+ if (nlines) return (lineDiv.offsetHeight / nlines) || 1;
+ measure.innerHTML = "x
";
+ return measure.firstChild.offsetHeight || 1;
}
- function charWidth() {return (measure.firstChild.offsetWidth || 320) / 40;}
function paddingTop() {return lineSpace.offsetTop;}
function paddingLeft() {return lineSpace.offsetLeft;}
function posFromMouse(e, liberal) {
- var off = eltOffset(lineSpace),
- x = e.pageX() - off.left,
- y = e.pageY() - off.top;
- if (!liberal && e.target() != lineSpace.parentNode && !(e.target() == wrapper && y > (lines.length * lineHeight())))
- for (var n = e.target(); n != lineDiv && n != cursor; n = n.parentNode)
- if (!n || n == wrapper) return null;
- var line = showingFrom + Math.floor(y / lineHeight());
- return clipPos({line: line, ch: charFromX(clipLine(line), x)});
+ var offW = eltOffset(scroller, true), x, y;
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+ try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
+ // This is a mess of a heuristic to try and determine whether a
+ // scroll-bar was clicked or not, and to return null if one was
+ // (and !liberal).
+ 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)});
}
function onContextMenu(e) {
var pos = posFromMouse(e);
if (!pos || window.opera) return; // Opera is difficult.
if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
- setCursor(pos.line, pos.ch);
+ operation(setCursor)(pos.line, pos.ch);
var oldCSS = input.style.cssText;
- input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.pageY() - 1) +
- "px; left: " + (e.pageX() - 1) + "px; z-index: 1000; background: white; " +
- "border-width: 0; outline: none; overflow: hidden;";
+ inputDiv.style.position = "absolute";
+ 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();
- input.focus();
- setSelRange(input, 0, val.length);
- if (gecko) e.stop();
- leaveInputAlone = true;
- setTimeout(function() {
- if (input.value != val) operation(replaceSelection)(input.value, "end");
+ focusInput();
+ setSelRange(input, 0, input.value.length);
+ function rehide() {
+ var newVal = splitLines(input.value).join("\n");
+ if (newVal != val) operation(replaceSelection)(newVal, "end");
+ inputDiv.style.position = "relative";
input.style.cssText = oldCSS;
leaveInputAlone = false;
prepareInput();
slowPoll();
- }, 50);
+ }
+
+ if (gecko) {
+ e_stop(e);
+ var mouseup = connect(window, "mouseup", function() {
+ mouseup();
+ setTimeout(rehide, 20);
+ }, true);
+ }
+ else {
+ setTimeout(rehide, 50);
+ }
}
// Cursor-blinking
@@ -1120,19 +1376,18 @@ var CodeMirror = (function() {
}
}
}
- for (var i = head.line, e = forward ? Math.min(i + 50, lines.length) : Math.max(0, i - 50); i != e; i+=d) {
+ 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;
var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
- if (found) {
- var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
- var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
- two = markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
- var clear = operation(function(){one(); two();});
- if (autoclear) setTimeout(clear, 800);
- else bracketHighlighted = clear;
- break;
- }
+ 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);
+ var clear = operation(function(){one.clear(); two && two.clear();});
+ if (autoclear) setTimeout(clear, 800);
+ else bracketHighlighted = clear;
}
// Finds the line to start with when starting a parse. Tries to
@@ -1148,7 +1403,7 @@ var CodeMirror = (function() {
if (line.stateAfter) return search;
var indented = line.indentation();
if (minline == null || minindent > indented) {
- minline = search;
+ minline = search - 1;
minindent = indented;
}
}
@@ -1163,11 +1418,21 @@ var CodeMirror = (function() {
line.highlight(mode, state);
line.stateAfter = copyState(mode, state);
}
- if (!lines[n].stateAfter) work.push(n);
+ changes.push({from: start, to: n});
+ if (n < lines.length && !lines[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];
+ 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;
else var task = work.pop();
@@ -1176,20 +1441,29 @@ var CodeMirror = (function() {
if (state) state = copyState(mode, state);
else state = startState(mode);
+ var unchanged = 0, compare = mode.compareStates, realChange = false;
for (var i = start, l = lines.length; i < l; ++i) {
var line = lines[i], hadState = line.stateAfter;
if (+new Date > end) {
work.push(i);
startWorker(options.workDelay);
- changes.push({from: task, to: i});
+ if (realChange) changes.push({from: task, to: i + 1});
return;
}
var changed = line.highlight(mode, state);
+ if (changed) realChange = true;
line.stateAfter = copyState(mode, state);
- if (hadState && !changed && line.text) break;
+ if (compare) {
+ if (hadState && compare(hadState, state)) break;
+ } else {
+ if (changed !== false || !hadState) unchanged = 0;
+ else if (++unchanged > 3) break;
+ }
}
- changes.push({from: task, to: i});
+ if (realChange) changes.push({from: task, to: i + 1});
}
+ if (foundWork && options.onHighlightComplete)
+ options.onHighlightComplete(instance);
}
function startWorker(time) {
if (!work.length) return;
@@ -1207,24 +1481,29 @@ 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.
- if (!leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged)))
+ if (focused && !leaveInputAlone &&
+ (updateInput === true || (updateInput !== false && selectionChanged)))
prepareInput();
- if (selectionChanged && options.onCursorActivity)
- options.onCursorActivity(instance);
- if (textChanged && options.onChange)
- options.onChange(instance);
if (selectionChanged && options.matchBrackets)
setTimeout(operation(function() {
if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
matchBrackets(false);
}), 20);
+ var tc = textChanged; // textChanged can be reset by cursoractivity callback
+ if (selectionChanged && options.onCursorActivity)
+ options.onCursorActivity(instance);
+ if (tc && options.onChange && instance)
+ options.onChange(instance, tc);
}
var nestedOperation = 0;
function operation(f) {
@@ -1259,6 +1538,7 @@ var CodeMirror = (function() {
var newmatch = line.match(query);
if (newmatch) match = newmatch;
else break;
+ start++;
}
}
else {
@@ -1338,9 +1618,21 @@ var CodeMirror = (function() {
},
from: function() {if (this.atOccurrence) return copyPos(this.pos.from);},
- to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}
+ to: function() {if (this.atOccurrence) return copyPos(this.pos.to);},
+
+ replace: function(newText) {
+ var self = this;
+ if (this.atOccurrence)
+ operation(function() {
+ self.pos.to = replaceRange(newText, self.pos.from, self.pos.to);
+ })();
+ }
};
+ for (var ext in extensions)
+ if (extensions.propertyIsEnumerable(ext) &&
+ !instance.propertyIsEnumerable(ext))
+ instance[ext] = extensions[ext];
return instance;
} // (end of function CodeMirror)
@@ -1348,6 +1640,7 @@ var CodeMirror = (function() {
CodeMirror.defaults = {
value: "",
mode: null,
+ theme: "default",
indentUnit: 2,
indentWithTabs: false,
tabMode: "classic",
@@ -1356,17 +1649,21 @@ var CodeMirror = (function() {
onKeyEvent: null,
lineNumbers: false,
gutter: false,
+ fixedGutter: false,
firstLineNumber: 1,
readOnly: false,
+ smartHome: true,
onChange: null,
onCursorActivity: null,
onGutterClick: null,
+ onHighlightComplete: null,
onFocus: null, onBlur: null, onScroll: null,
matchBrackets: false,
workTime: 100,
workDelay: 200,
undoDepth: 40,
- tabindex: null
+ tabindex: null,
+ document: window.document
};
// Known modes, by name and by MIME
@@ -1383,15 +1680,15 @@ var CodeMirror = (function() {
spec = mimeModes[spec];
if (typeof spec == "string")
var mname = spec, config = {};
- else
+ else if (spec != null)
var mname = spec.name, config = spec;
var mfactory = modes[mname];
if (!mfactory) {
if (window.console) console.warn("No mode " + mname + " found, falling back to plain text.");
return CodeMirror.getMode(options, "text/plain");
}
- return mfactory(options, config);
- }
+ return mfactory(options, config || {});
+ };
CodeMirror.listModes = function() {
var list = [];
for (var m in modes)
@@ -1401,10 +1698,15 @@ 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;
};
+ var extensions = {};
+ CodeMirror.defineExtension = function(name, func) {
+ extensions[name] = func;
+ };
+
CodeMirror.fromTextArea = function(textarea, options) {
if (!options) options = {};
options.value = textarea.value;
@@ -1484,7 +1786,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;
},
@@ -1517,6 +1819,7 @@ var CodeMirror = (function() {
},
current: function(){return this.string.slice(this.start, this.pos);}
};
+ CodeMirror.StringStream = StringStream;
// Line objects. These hold state related to a line, including
// highlighting info (the styles array).
@@ -1526,10 +1829,23 @@ var CodeMirror = (function() {
this.text = text;
this.marked = this.gutterMarker = this.className = 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) {
+ var newmk = ln.marked || (ln.marked = []), mark = mk[i];
+ newmk.push({from: null, to: null, style: mark.style, set: mark.set});
+ mark.set.push(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) {
+ 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);
@@ -1538,39 +1854,86 @@ var CodeMirror = (function() {
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;}
+ var changeStart = Math.min(from, from + 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--;}
+ if (mark.from != null && mark.from >= end) del = true;
+ else {
+ if (mark.from != null && mark.from >= from) {
+ mark.from += diff;
+ if (mark.from <= 0) mark.from = from == null ? null : 0;
+ }
+ else if (to_ == null) mark.to = null;
+ if (mark.to != null && mark.to > from) {
+ mark.to += diff;
+ if (mark.to < 0) del = true;
+ }
+ }
+ if (del || (mark.from != null && mark.to != null && mark.from >= mark.to)) 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];
+ if (mark.to > pos || mark.to == null) {
+ if (!taken.marked) taken.marked = [];
+ taken.marked.push({
+ from: mark.from < pos || mark.from == null ? null : mark.from - pos + textBefore.length,
+ to: mark.to == null ? null : mark.to - pos + textBefore.length,
+ style: mark.style, set: mark.set
+ });
+ mark.set.push(taken);
+ }
+ }
+ }
+ return taken;
},
- addMark: function(from, to, style) {
- var mk = this.marked, mark = {from: from, to: to, style: style};
+ append: function(line) {
+ if (!line.text.length) return;
+ var mylen = this.text.length, mk = line.marked;
+ this.text += line.text;
+ copyStyles(0, line.text.length, line.styles, this.styles);
+ if (mk && mk.length) {
+ var mymk = this.marked || (this.marked = []);
+ for (var i = 0; i < mymk.length; ++i)
+ if (mymk[i].to == null) mymk[i].to = mylen;
+ 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.set == mark.set) {
+ mymark.to = mark.to == null ? null : mark.to + mylen;
+ continue outer;
+ }
+ }
+ }
+ mymk.push(mark);
+ mark.set.push(this);
+ mark.from += mylen;
+ if (mark.to != null) mark.to += mylen;
+ }
+ }
+ },
+ addMark: function(from, to, style, set) {
+ set.push(this);
if (this.marked == null) this.marked = [];
- this.marked.push(mark);
- this.marked.sort(function(a, b){return a.from - b.from;});
- return mark;
- },
- removeMark: function(mark) {
- var mk = this.marked;
- if (!mk) return;
- for (var i = 0; i < mk.length; ++i)
- if (mk[i] == mark) {mk.splice(i, 1); break;}
+ this.marked.push({from: from, to: to, style: style, set: set});
+ 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
// classes.
highlight: function(mode, state) {
- var stream = new StringStream(this.text), st = this.styles, pos = 0, changed = false;
+ var stream = new StringStream(this.text), st = this.styles, pos = 0;
+ var changed = false, curWord = st[0], prevWord;
+ if (this.text == "" && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
var style = mode.token(stream, state);
var substr = this.text.slice(stream.start, stream.pos);
@@ -1578,8 +1941,9 @@ var CodeMirror = (function() {
if (pos && st[pos-1] == style)
st[pos-2] += substr;
else if (substr) {
- if (!changed && st[pos] != substr || st[pos+1] != style) changed = true;
+ if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
st[pos++] = substr; st[pos++] = style;
+ prevWord = curWord; curWord = st[pos];
}
// Give up when line is ridiculously long
if (stream.pos > 5000) {
@@ -1588,7 +1952,11 @@ var CodeMirror = (function() {
}
}
if (st.length != pos) {st.length = pos; changed = true;}
- return changed;
+ if (pos && st[pos-2] != prevWord) changed = true;
+ // 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).
@@ -1607,7 +1975,7 @@ var CodeMirror = (function() {
indentation: function() {return countColumn(this.text);},
// Produces an HTML fragment for the line, taking selection,
// marking, and highlighting into account.
- getHTML: function(sfrom, sto, includePre) {
+ getHTML: function(sfrom, sto, includePre, endAt) {
var html = [];
if (includePre)
html.push(this.className ? '': "");
@@ -1618,11 +1986,18 @@ var CodeMirror = (function() {
}
var st = this.styles, allText = this.text, marked = this.marked;
if (sfrom == sto) sfrom = null;
+ var len = allText.length;
+ if (endAt != null) len = Math.min(endAt, len);
- if (!allText)
+ if (!allText && endAt == null)
span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
else if (!marked && sfrom == null)
- for (var i = 0, e = st.length; i < e; i+=2) span(st[i], st[i+1]);
+ for (var i = 0, ch = 0; ch < len; i+=2) {
+ var str = st[i], style = st[i+1], l = str.length;
+ if (ch + l > len) str = str.slice(0, len - ch);
+ ch += l;
+ span(str, style && "cm-" + style);
+ }
else {
var pos = 0, i = 0, text = "", style, sg = 0;
var markpos = -1, mark = null;
@@ -1632,9 +2007,9 @@ var CodeMirror = (function() {
mark = (markpos < marked.length) ? marked[markpos] : null;
}
}
- nextMark();
- while (pos < allText.length) {
- var upto = allText.length;
+ nextMark();
+ while (pos < len) {
+ var upto = len;
var extraStyle = "";
if (sfrom != null) {
if (sfrom > pos) upto = sfrom;
@@ -1653,12 +2028,12 @@ var CodeMirror = (function() {
}
for (;;) {
var end = pos + text.length;
- var apliedStyle = style;
- if (extraStyle) apliedStyle = style ? style + extraStyle : extraStyle;
- span(end > upto ? text.slice(0, upto - pos) : text, apliedStyle);
+ var appliedStyle = style;
+ if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
+ span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
pos = end;
- text = st[i++]; style = st[i++];
+ text = st[i++]; style = "cm-" + st[i++];
}
}
if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
@@ -1716,42 +2091,34 @@ var CodeMirror = (function() {
}
};
- // Event stopping compatibility wrapper.
- function stopEvent() {
- if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
- else {this.returnValue = false; this.cancelBubble = true;}
- }
+ function stopMethod() {e_stop(this);}
// Ensure an event has a stop method.
function addStop(event) {
- if (!event.stop) event.stop = stopEvent;
+ if (!event.stop) event.stop = stopMethod;
return event;
}
- // Event wrapper, exposing the few operations we need.
- function Event(orig) {this.e = orig;}
- Event.prototype = {
- stop: function() {stopEvent.call(this.e);},
- target: function() {return this.e.target || this.e.srcElement;},
- button: function() {
- if (this.e.which) return this.e.which;
- else if (this.e.button & 1) return 1;
- else if (this.e.button & 2) return 3;
- else if (this.e.button & 4) return 2;
- },
- pageX: function() {
- if (this.e.pageX != null) return this.e.pageX;
- else return this.e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
- },
- pageY: function() {
- if (this.e.pageY != null) return this.e.pageY;
- else return this.e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
- }
- };
+ function e_preventDefault(e) {
+ if (e.preventDefault) e.preventDefault();
+ else e.returnValue = false;
+ }
+ function e_stopPropagation(e) {
+ if (e.stopPropagation) e.stopPropagation();
+ else e.cancelBubble = true;
+ }
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
+ function e_target(e) {return e.target || e.srcElement;}
+ function e_button(e) {
+ if (e.which) return e.which;
+ else if (e.button & 1) return 1;
+ else if (e.button & 2) return 3;
+ else if (e.button & 4) return 2;
+ }
// Event handler registration. If disconnect is true, it'll return a
// function that unregisters the handler.
function connect(node, type, handler, disconnect) {
- function wrapHandler(event) {handler(new Event(event || window.event));}
+ function wrapHandler(event) {handler(event || window.event);}
if (typeof node.addEventListener == "function") {
node.addEventListener(type, wrapHandler, false);
if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);};
@@ -1772,7 +2139,18 @@ var CodeMirror = (function() {
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 "ondragstart" in div && "ondrop" 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 lineSep = "\n";
// Feature-detect whether newlines in textareas are converted to \r\n
@@ -1802,11 +2180,23 @@ var CodeMirror = (function() {
return n;
}
+ function computedStyle(elt) {
+ if (elt.currentStyle) return elt.currentStyle;
+ return window.getComputedStyle(elt, null);
+ }
// Find the position of an element by following the offsetParent chain.
- function eltOffset(node) {
- var x = 0, y = 0, n2 = node;
- for (var n = node; n; n = n.offsetParent) {x += n.offsetLeft; y += n.offsetTop;}
- for (var n = node; n != document.body; n = n.parentNode) {x -= n.scrollLeft; y -= n.scrollTop;}
+ // 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;
+ for (var n = node; n; n = n.offsetParent) {
+ x += n.offsetLeft; y += n.offsetTop;
+ if (screen && computedStyle(n).position == "fixed")
+ skipDoc = true;
+ }
+ var e = screen && !skipDoc ? null : doc;
+ 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};
}
// Get a node's text content.
@@ -1819,9 +2209,18 @@ 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("pre");
function htmlEscape(str) {
- return str.replace(/[<&]/g, function(str) {return 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
// last edited character.
@@ -1842,8 +2241,9 @@ var CodeMirror = (function() {
// See if "".split is the broken IE version, if so, provide an
// alternative way to split lines.
+ var splitLines, selRange, setSelRange;
if ("\n\nb".split(/\n/).length != 3)
- var splitLines = function(string) {
+ splitLines = function(string) {
var pos = 0, nl, result = [];
while ((nl = string.indexOf("\n", pos)) > -1) {
result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
@@ -1853,23 +2253,40 @@ var CodeMirror = (function() {
return result;
};
else
- var splitLines = function(string){return string.split(/\r?\n/);};
+ splitLines = function(string){return string.split(/\r?\n/);};
+ CodeMirror.splitLines = splitLines;
// Sane model of finding and setting the selection in a textarea
if (window.getSelection) {
- var selRange = function(te) {
+ selRange = function(te) {
try {return {start: te.selectionStart, end: te.selectionEnd};}
catch(e) {return null;}
};
- var setSelRange = function(te, start, end) {
- try {te.setSelectionRange(start, end);}
- catch(e) {} // Fails on Firefox when textarea isn't part of the document
- };
+ if (safari)
+ // 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
+ // the left. If you press shift-right, the anchor ends up at the
+ // front. This is not what CodeMirror wants, so it does a
+ // spurious modify() call to get out of limbo.
+ setSelRange = function(te, start, end) {
+ if (start == end)
+ te.setSelectionRange(start, end);
+ else {
+ te.setSelectionRange(start, end - 1);
+ window.getSelection().modify("extend", "forward", "character");
+ }
+ };
+ else
+ setSelRange = function(te, start, end) {
+ try {te.setSelectionRange(start, end);}
+ catch(e) {} // Fails on Firefox when textarea isn't part of the document
+ };
}
// IE model. Don't ask.
else {
- var selRange = function(te) {
- try {var range = document.selection.createRange();}
+ selRange = function(te) {
+ try {var range = te.ownerDocument.selection.createRange();}
catch(e) {return null;}
if (!range || range.parentElement() != te) return null;
var val = te.value, len = val.length, localRange = te.createTextRange();
@@ -1890,7 +2307,7 @@ var CodeMirror = (function() {
for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {}
return {start: start, end: end};
};
- var setSelRange = function(te, start, end) {
+ setSelRange = function(te, start, end) {
var range = te.createTextRange();
range.collapse(true);
var endrange = range.duplicate();
diff --git a/rhodecode/templates/admin/repos/repo_edit_perms.html b/rhodecode/templates/admin/repos/repo_edit_perms.html
--- a/rhodecode/templates/admin/repos/repo_edit_perms.html
+++ b/rhodecode/templates/admin/repos/repo_edit_perms.html
@@ -1,4 +1,4 @@
-
+
| ${_('none')} |
${_('read')} |
diff --git a/rhodecode/templates/base/base.html b/rhodecode/templates/base/base.html
--- a/rhodecode/templates/base/base.html
+++ b/rhodecode/templates/base/base.html
@@ -149,60 +149,6 @@
${_('loading...')}
-
@@ -231,38 +177,15 @@
-
+
${_('Switch to')}
-
- -
- ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
-
- %if c.rhodecode_repo.branches.values():
- %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
- - ${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}
- %endfor
- %else:
- - ${h.link_to(_('There are no branches yet'),'#')}
- %endif
-
-
- -
- ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
-
- %if c.rhodecode_repo.tags.values():
- %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
- - ${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}
- %endfor
- %else:
- - ${h.link_to(_('There are no tags yet'),'#')}
- %endif
-
-
-
+
@@ -329,8 +252,73 @@
${c.repository_forks}
-
+
%else:
##ROOT MENU
@@ -373,5 +361,5 @@
%endif
- %endif
+ %endif
%def>
diff --git a/rhodecode/templates/base/root.html b/rhodecode/templates/base/root.html
--- a/rhodecode/templates/base/root.html
+++ b/rhodecode/templates/base/root.html
@@ -129,10 +129,16 @@
YUD.addClass(menu,'hidden');
}
})
-
+ YUE.on(window,'scroll',function(){
+ if(YUD.getDocumentScrollTop() > 45){
+ YUD.addClass('header-inner','hover');
+ }
+ else{
+ YUD.removeClass('header-inner','hover');
+ }
+ })
})
-
%def>
<%def name="js_extra()">
%def>
diff --git a/rhodecode/templates/changeset/changeset_range.html b/rhodecode/templates/changeset/changeset_range.html
--- a/rhodecode/templates/changeset/changeset_range.html
+++ b/rhodecode/templates/changeset/changeset_range.html
@@ -37,7 +37,7 @@
-
+
%for cs in c.cs_ranges:
|
diff --git a/rhodecode/templates/files/files_add.html b/rhodecode/templates/files/files_add.html
--- a/rhodecode/templates/files/files_add.html
+++ b/rhodecode/templates/files/files_add.html
@@ -39,36 +39,35 @@
${h.form(h.url.current(),method='post',id='eform',enctype="multipart/form-data")}
${_('Add new file')}
-
@@ -319,7 +193,6 @@
%if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
%endif
-
%else:
${_('Loaded in')} ${c.stats_percentage} %
%endif
@@ -331,333 +204,9 @@
-
-
@@ -669,32 +218,461 @@
<%include file='../shortlog/shortlog_data.html'/>
- ##%if c.repo_changesets:
- ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
- ##%endif
+
+
+
+%if c.readme_data:
+
-
-
-
${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}
-
-
- <%include file='../tags/tags_data.html'/>
- %if c.repo_changesets:
- ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
- %endif
-
-
-
-
-
${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}
-
-
- <%include file='../branches/branches_data.html'/>
- %if c.repo_changesets:
- ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
- %endif
-
-
+%endif
+
+
+
%def>
diff --git a/rhodecode/templates/switch_to_list.html b/rhodecode/templates/switch_to_list.html
new file mode 100644
--- /dev/null
+++ b/rhodecode/templates/switch_to_list.html
@@ -0,0 +1,25 @@
+## -*- coding: utf-8 -*-
+
+ ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
+
+ %if c.rhodecode_repo.branches.values():
+ %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
+ - ${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}
+ %endfor
+ %else:
+ - ${h.link_to(_('There are no branches yet'),'#')}
+ %endif
+
+
+
+ ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
+
+ %if c.rhodecode_repo.tags.values():
+ %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
+ - ${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}
+ %endfor
+ %else:
+ - ${h.link_to(_('There are no tags yet'),'#')}
+ %endif
+
+
\ No newline at end of file
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -24,7 +24,9 @@ requirements = [
"python-dateutil>=1.5.0,<2.0.0",
"dulwich>=0.8.0,<0.9.0",
"vcs>=0.2.3.dev",
- "webob==1.0.8"
+ "webob==1.0.8",
+ "markdown==2.0.3",
+ "docutils==0.8.1",
]
dependency_links = [
diff --git a/test.ini b/test.ini
--- a/test.ini
+++ b/test.ini
@@ -88,21 +88,27 @@ beaker.cache.regions=super_short_term,sh
beaker.cache.super_short_term.type=memory
beaker.cache.super_short_term.expire=10
+beaker.cache.super_short_term.key_length = 256
beaker.cache.short_term.type=memory
beaker.cache.short_term.expire=60
+beaker.cache.short_term.key_length = 256
beaker.cache.long_term.type=memory
beaker.cache.long_term.expire=36000
+beaker.cache.long_term.key_length = 256
beaker.cache.sql_cache_short.type=memory
beaker.cache.sql_cache_short.expire=10
+beaker.cache.sql_cache_short.key_length = 256
beaker.cache.sql_cache_med.type=memory
beaker.cache.sql_cache_med.expire=360
+beaker.cache.sql_cache_med.key_length = 256
beaker.cache.sql_cache_long.type=file
beaker.cache.sql_cache_long.expire=3600
+beaker.cache.sql_cache_long.key_length = 256
####################################
### BEAKER SESSION ####
@@ -143,7 +149,7 @@ logview.pylons.util = #eee
#########################################################
sqlalchemy.db1.url = sqlite:///%(here)s/test.db
#sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
-#sqlalchemy.db1.echo = False
+#sqlalchemy.db1.echo = false
#sqlalchemy.db1.pool_recycle = 3600
sqlalchemy.convert_unicode = true