pasm
Version:
Piston X86-64 Assembler
1,153 lines (1,087 loc) • 131 kB
JavaScript
// CodeMirror version 2.36
//
// 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.
// CodeMirror is the only global var we claim
window.CodeMirror = (function() {
"use strict";
// This is the function that produces an editor instance. Its
// closure is used to store the editor state.
function CodeMirror(place, givenOptions) {
// Determine effective options based on given values and defaults.
var options = {}, defaults = CodeMirror.defaults;
for (var opt in defaults)
if (defaults.hasOwnProperty(opt))
options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em");
input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
// Wraps and hides input textarea
var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
// The empty scrollbar content, used solely for managing the scrollbar thumb.
var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner");
// The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar");
// DIVs containing the selection and the actual code
var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1");
// Blinky cursor, and element used to ensure cursor fits at the end of a line
var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden");
// Used to measure text size
var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;");
var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0");
var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter");
// Moved around its parent to cover visible view
var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
// Set to the height of the text, causes scrolling
var sizer = elt("div", [mover], null, "position: relative");
// Provides scrolling
var scroller = elt("div", [sizer], "CodeMirror-scroll");
scroller.setAttribute("tabIndex", "-1");
// The element in which the editor lives.
var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""));
if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
themeChanged(); keyMapChanged();
// Needed to hide big blue blinking cursor on Mobile Safari
if (ios) input.style.width = "0px";
if (!webkit) scroller.draggable = true;
lineSpace.style.outline = "none";
if (options.tabindex != null) input.tabIndex = options.tabindex;
if (options.autofocus) focusInput();
if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
// Needed to handle Tab key in KHTML
if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
// Check for OS X >= 10.7. This has transparent scrollbars, so the
// overlaying of one scrollbar with another won't work. This is a
// temporary hack to simply turn off the overlay scrollbar. See
// issue #727.
if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; }
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
else if (ie_lt8) scrollbar.style.minWidth = "18px";
// 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. doc is the tree of Line objects,
// frontier is the point up to which the content has been parsed,
// and history the undo history (instance of History constructor).
var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused;
loadMode();
// The selection. These are always maintained to point at valid
// positions. Inverted is used to remember that the user is
// selecting bottom-to-top.
var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
// Selection-related flags. shiftSelecting obviously tracks
// whether the user is holding shift.
var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText,
overwrite = false, suppressEdits = false, pasteIncoming = false;
// Variables used by startOperation/endOperation to track what
// happened during the operation.
var updateInput, userSelChange, changes, textChanged, selectionChanged,
gutterDirty, callbacks;
// Current visible range (may be bigger than the view window).
var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
// bracketHighlighted is used to remember that a bracket has been
// marked.
var bracketHighlighted;
// Tracks the maximum line length so that the horizontal scrollbar
// can be kept static when scrolling.
var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true;
var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
var goalColumn = null;
// Initialize the content.
operation(function(){setValue(options.value || ""); updateInput = false;})();
var history = new History();
// Register our event handlers.
connect(scroller, "mousedown", operation(onMouseDown));
connect(scroller, "dblclick", operation(onDoubleClick));
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(scroller, "scroll", onScrollMain);
connect(scrollbar, "scroll", onScrollBar);
connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
var resizeHandler = connect(window, "resize", function() {
if (wrapper.parentNode) updateDisplay(true);
else resizeHandler();
}, true);
connect(input, "keyup", operation(onKeyUp));
connect(input, "input", fastPoll);
connect(input, "keydown", operation(onKeyDown));
connect(input, "keypress", operation(onKeyPress));
connect(input, "focus", onFocus);
connect(input, "blur", onBlur);
function drag_(e) {
if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
e_stop(e);
}
if (options.dragDrop) {
connect(scroller, "dragstart", onDragStart);
connect(scroller, "dragenter", drag_);
connect(scroller, "dragover", drag_);
connect(scroller, "drop", operation(onDrop));
}
connect(scroller, "paste", function(){focusInput(); fastPoll();});
connect(input, "paste", function(){pasteIncoming = true; fastPoll();});
connect(input, "cut", operation(function(){
if (!options.readOnly) replaceSelection("");
}));
// Needed to handle Tab key in KHTML
if (khtml) connect(sizer, "mouseup", function() {
if (document.activeElement == input) input.blur();
focusInput();
});
// IE throws unspecified error in certain cases, when
// trying to access activeElement before onload
var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
else onBlur();
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 = wrapper.CodeMirror = {
getValue: getValue,
setValue: operation(setValue),
getSelection: getSelection,
replaceSelection: operation(replaceSelection),
focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
setOption: function(option, value) {
var oldVal = options[option];
options[option] = value;
if (option == "mode" || option == "indentUnit") loadMode();
else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
else if (option == "readOnly" && !value) {resetInput(true);}
else if (option == "theme") themeChanged();
else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
else if (option == "tabSize") updateDisplay(true);
else if (option == "keyMap") keyMapChanged();
else if (option == "tabindex") input.tabIndex = value;
if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" ||
option == "theme" || option == "lineNumberFormatter") {
gutterChanged();
updateDisplay(true);
}
},
getOption: function(option) {return options[option];},
getMode: function() {return mode;},
undo: operation(undo),
redo: operation(redo),
indentLine: operation(function(n, dir) {
if (typeof dir != "string") {
if (dir == null) dir = options.smartIndent ? "smart" : "prev";
else dir = dir ? "add" : "subtract";
}
if (isLine(n)) indentLine(n, dir);
}),
indentSelection: operation(indentSelected),
historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
clearHistory: function() {history = new History();},
setHistory: function(histData) {
history = new History();
history.done = histData.done;
history.undone = histData.undone;
},
getHistory: function() {
function cp(arr) {
for (var i = 0, nw = [], nwelt; i < arr.length; ++i) {
nw.push(nwelt = []);
for (var j = 0, elt = arr[i]; j < elt.length; ++j) {
var old = [], cur = elt[j];
nwelt.push({start: cur.start, added: cur.added, old: old});
for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k]));
}
}
return nw;
}
return {done: cp(history.done), undone: cp(history.undone)};
},
matchBrackets: operation(function(){matchBrackets(true);}),
getTokenAt: operation(function(pos) {
pos = clipPos(pos);
return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch);
}),
getStateAfter: function(line) {
line = clipLine(line == null ? doc.size - 1: line);
return getStateBefore(line + 1);
},
cursorCoords: function(start, mode) {
if (start == null) start = sel.inverted;
return this.charCoords(start ? sel.from : sel.to, mode);
},
charCoords: function(pos, mode) {
pos = clipPos(pos);
if (mode == "local") return localCoords(pos, false);
if (mode == "div") return localCoords(pos, true);
return pageCoords(pos);
},
coordsChar: function(coords) {
var off = eltOffset(lineSpace);
return coordsChar(coords.x - off.left, coords.y - off.top);
},
defaultTextHeight: function() { return textHeight(); },
markText: operation(markText),
setBookmark: setBookmark,
findMarksAt: findMarksAt,
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);}),
onDeleteLine: function(line, f) {
if (typeof line == "number") {
if (!isLine(line)) return null;
line = getLine(line);
}
(line.handlers || (line.handlers = [])).push(f);
return line;
},
lineInfo: lineInfo,
getViewport: function() { return {from: showingFrom, to: showingTo};},
addWidget: function(pos, node, scroll, vert, horiz) {
pos = localCoords(clipPos(pos));
var top = pos.yBot, left = pos.x;
node.style.position = "absolute";
sizer.appendChild(node);
if (vert == "over") top = pos.y;
else if (vert == "near") {
var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
hspace = Math.max(sizer.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 = sizer.clientWidth - node.offsetWidth;
node.style.right = "0px";
} else {
if (horiz == "left") left = 0;
else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2;
node.style.left = (left + paddingLeft()) + "px";
}
if (scroll)
scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
},
lineCount: function() {return doc.size;},
clipPos: clipPos,
getCursor: function(start) {
if (start == null) start = sel.inverted;
return copyPos(start ? sel.from : sel.to);
},
somethingSelected: function() {return !posEq(sel.from, sel.to);},
setCursor: operation(function(line, ch, user) {
if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
else setCursor(line, ch, user);
}),
setSelection: operation(function(from, to, user) {
(user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
}),
getLine: function(line) {if (isLine(line)) return getLine(line).text;},
getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
setLine: operation(function(line, text) {
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}));
}),
replaceRange: operation(replaceRange),
getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
triggerOnKeyDown: operation(onKeyDown),
execCommand: function(cmd) {return commands[cmd](instance);},
// Stuff used by commands, probably not much use to outside code.
moveH: operation(moveH),
deleteH: operation(deleteH),
moveV: operation(moveV),
toggleOverwrite: function() {
if(overwrite){
overwrite = false;
cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
} else {
overwrite = true;
cursor.className += " CodeMirror-overwrite";
}
},
posFromIndex: 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});
},
indexFromPos: function (coords) {
if (coords.line < 0 || coords.ch < 0) return 0;
var index = coords.ch;
doc.iter(0, coords.line, function (line) {
index += line.text.length + 1;
});
return index;
},
scrollTo: function(x, y) {
if (x != null) scroller.scrollLeft = x;
if (y != null) scrollbar.scrollTop = scroller.scrollTop = y;
updateDisplay([]);
},
getScrollInfo: function() {
return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
height: scrollbar.scrollHeight, width: scroller.scrollWidth};
},
scrollIntoView: function(pos) {
var coords = localCoords(pos ? clipPos(pos) : sel.inverted ? sel.from : sel.to);
scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
},
setSize: function(width, height) {
function interpret(val) {
val = String(val);
return /^\d+$/.test(val) ? val + "px" : val;
}
if (width != null) wrapper.style.width = interpret(width);
if (height != null) scroller.style.height = interpret(height);
instance.refresh();
},
operation: function(f){return operation(f)();},
compoundChange: function(f){return compoundChange(f);},
refresh: function(){
updateDisplay(true, null, lastScrollTop);
if (scrollbar.scrollHeight > lastScrollTop)
scrollbar.scrollTop = lastScrollTop;
},
getInputField: function(){return input;},
getWrapperElement: function(){return wrapper;},
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 lineContent(line, wrapAt) {
if (!line.styles)
line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize);
return line.getContent(options.tabSize, wrapAt, options.lineWrapping);
}
function setValue(code) {
var top = {line: 0, ch: 0};
updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
splitLines(code), top, top);
updateInput = true;
}
function getValue(lineSep) {
var text = [];
doc.iter(0, doc.size, function(line) { text.push(line.text); });
return text.join(lineSep || "\n");
}
function onScrollBar(e) {
if (Math.abs(scrollbar.scrollTop - lastScrollTop) > 1) {
lastScrollTop = scroller.scrollTop = scrollbar.scrollTop;
updateDisplay([]);
}
}
function onScrollMain(e) {
if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px")
gutter.style.left = scroller.scrollLeft + "px";
if (Math.abs(scroller.scrollTop - lastScrollTop) > 1) {
lastScrollTop = scroller.scrollTop;
if (scrollbar.scrollTop != lastScrollTop)
scrollbar.scrollTop = lastScrollTop;
updateDisplay([]);
}
if (options.onScroll) options.onScroll(instance);
}
function onMouseDown(e) {
setShift(e_prop(e, "shiftKey"));
// Check whether this is a click in a widget
for (var n = e_target(e); n != wrapper; n = n.parentNode)
if (n.parentNode == sizer && n != mover) return;
// 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, e);
return e_preventDefault(e);
}
var start = posFromMouse(e);
switch (e_button(e)) {
case 3:
if (gecko) onContextMenu(e);
return;
case 2:
if (start) setCursor(start.line, start.ch, true);
setTimeout(focusInput, 20);
e_preventDefault(e);
return;
}
// For button 1, if it was clicked inside the editor
// (posFromMouse returning non-null), we have to adjust the
// selection.
if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
if (!focused) onFocus();
var now = +new Date, type = "single";
if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
type = "triple";
e_preventDefault(e);
setTimeout(focusInput, 20);
selectLine(start.line);
} else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
type = "double";
lastDoubleClick = {time: now, pos: start};
e_preventDefault(e);
var word = findWordAt(start);
setSelectionUser(word.from, word.to);
} else { lastClick = {time: now, pos: start}; }
function dragEnd(e2) {
if (webkit) scroller.draggable = false;
draggingText = false;
up(); drop();
if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
e_preventDefault(e2);
setCursor(start.line, start.ch, true);
focusInput();
}
}
var last = start, going;
if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
!posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
// Let the drag handler handle this.
if (webkit) scroller.draggable = true;
var up = connect(document, "mouseup", operation(dragEnd), true);
var drop = connect(scroller, "drop", operation(dragEnd), true);
draggingText = true;
// IE's approach to draggable
if (scroller.dragDrop) scroller.dragDrop();
return;
}
e_preventDefault(e);
if (type == "single") setCursor(start.line, start.ch, true);
var startstart = sel.from, startend = sel.to;
function doSelect(cur) {
if (type == "single") {
setSelectionUser(start, cur);
} else if (type == "double") {
var word = findWordAt(cur);
if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
else setSelectionUser(startstart, word.to);
} else if (type == "triple") {
if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
}
}
function extend(e) {
var cur = posFromMouse(e, true);
if (cur && !posEq(cur, last)) {
if (!focused) onFocus();
last = cur;
doSelect(cur);
updateInput = false;
var visible = visibleLines();
if (cur.line >= visible.to || cur.line < visible.from)
going = setTimeout(operation(function(){extend(e);}), 150);
}
}
function done(e) {
clearTimeout(going);
var cur = posFromMouse(e);
if (cur) doSelect(cur);
e_preventDefault(e);
focusInput();
updateInput = true;
move(); up();
}
var move = connect(document, "mousemove", operation(function(e) {
clearTimeout(going);
e_preventDefault(e);
if (!ie && !e_button(e)) done(e);
else extend(e);
}), true);
var up = connect(document, "mouseup", operation(done), true);
}
function onDoubleClick(e) {
for (var n = e_target(e); n != wrapper; n = n.parentNode)
if (n.parentNode == gutterText) return e_preventDefault(e);
e_preventDefault(e);
}
function onDrop(e) {
if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
e_preventDefault(e);
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;
var loadFile = function(file, i) {
var reader = new FileReader;
reader.onload = function() {
text[i] = reader.result;
if (++read == n) {
pos = clipPos(pos);
operation(function() {
var end = replaceRange(text.join(""), pos, pos);
setSelectionUser(pos, end);
})();
}
};
reader.readAsText(file);
};
for (var i = 0; i < n; ++i) loadFile(files[i], i);
} else {
// Don't do a replace if the drop happened inside of the selected text.
if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
try {
var text = e.dataTransfer.getData("Text");
if (text) {
compoundChange(function() {
var curFrom = sel.from, curTo = sel.to;
setSelectionUser(pos, pos);
if (draggingText) replaceRange("", curFrom, curTo);
replaceSelection(text);
focusInput();
});
}
}
catch(e){}
}
}
function onDragStart(e) {
var txt = getSelection();
e.dataTransfer.setData("Text", txt);
// Use dummy image instead of default browsers image.
if (e.dataTransfer.setDragImage)
e.dataTransfer.setDragImage(elt('img'), 0, 0);
}
function doHandleBinding(bound, dropShift) {
if (typeof bound == "string") {
bound = commands[bound];
if (!bound) return false;
}
var prevShift = shiftSelecting;
try {
if (options.readOnly) suppressEdits = true;
if (dropShift) shiftSelecting = null;
bound(instance);
} catch(e) {
if (e != Pass) throw e;
return false;
} finally {
shiftSelecting = prevShift;
suppressEdits = false;
}
return true;
}
var maybeTransition;
function handleKeyBinding(e) {
// Handle auto keymap transitions
var startMap = getKeyMap(options.keyMap), next = startMap.auto;
clearTimeout(maybeTransition);
if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
if (getKeyMap(options.keyMap) == startMap) {
options.keyMap = (next.call ? next.call(null, instance) : next);
}
}, 50);
var name = keyNames[e_prop(e, "keyCode")], handled = false;
var flipCtrlCmd = opera && mac;
if (name == null || e.altGraphKey) return false;
if (e_prop(e, "altKey")) name = "Alt-" + name;
if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
var stopped = false;
function stop() { stopped = true; }
if (e_prop(e, "shiftKey")) {
handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
function(b) {return doHandleBinding(b, true);}, stop)
|| lookupKey(name, options.extraKeys, options.keyMap, function(b) {
if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
}, stop);
} else {
handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
}
if (stopped) handled = false;
if (handled) {
e_preventDefault(e);
restartBlink();
if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
}
return handled;
}
function handleCharBinding(e, ch) {
var handled = lookupKey("'" + ch + "'", options.extraKeys,
options.keyMap, function(b) { return doHandleBinding(b, true); });
if (handled) {
e_preventDefault(e);
restartBlink();
}
return handled;
}
var lastStoppedKey = null;
function onKeyDown(e) {
if (!focused) onFocus();
if (ie && e.keyCode == 27) { e.returnValue = false; }
if (pollingFast) { if (readInput()) pollingFast = false; }
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
var code = e_prop(e, "keyCode");
// IE does strange things with escape.
setShift(code == 16 || e_prop(e, "shiftKey"));
// First give onKeyEvent option a chance to handle this.
var handled = handleKeyBinding(e);
if (opera) {
lastStoppedKey = handled ? code : null;
// Opera has no cut event... we try to at least catch the key combo
if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
replaceSelection("");
}
}
function onKeyPress(e) {
if (pollingFast) readInput();
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
if (mode.electricChars.indexOf(ch) > -1)
setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
}
if (handleCharBinding(e, ch)) return;
fastPoll();
}
function onKeyUp(e) {
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
}
function onFocus() {
if (options.readOnly == "nocursor") return;
if (!focused) {
if (options.onFocus) options.onFocus(instance);
focused = true;
if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
scroller.className += " CodeMirror-focused";
}
slowPoll();
restartBlink();
}
function onBlur() {
if (focused) {
if (options.onBlur) options.onBlur(instance);
focused = false;
if (bracketHighlighted)
operation(function(){
if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
})();
scroller.className = scroller.className.replace(" CodeMirror-focused", "");
}
clearInterval(blinker);
setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
}
// Replace the range from from to to by the strings in newText.
// Afterwards, set the selection to selFrom, selTo.
function updateLines(from, to, newText, selFrom, selTo) {
if (suppressEdits) return;
var old = [];
doc.iter(from.line, to.line + 1, function(line) {
old.push(newHL(line.text, line.markedSpans));
});
if (history) {
history.addChange(from.line, newText.length, old);
while (history.done.length > options.undoDepth) history.done.shift();
}
var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText);
updateLinesNoUndo(from, to, lines, selFrom, selTo);
}
function unredoHelper(from, to) {
if (!from.length) return;
var set = from.pop(), out = [];
for (var i = set.length - 1; i >= 0; i -= 1) {
var change = set[i];
var replaced = [], end = change.start + change.added;
doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); });
out.push({start: change.start, added: change.old.length, old: replaced});
var pos = {line: change.start + change.old.length - 1,
ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))};
updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length},
change.old, pos, pos);
}
updateInput = true;
to.push(out);
}
function undo() {unredoHelper(history.done, history.undone);}
function redo() {unredoHelper(history.undone, history.done);}
function updateLinesNoUndo(from, to, lines, selFrom, selTo) {
if (suppressEdits) return;
var recomputeMaxLength = false, maxLineLength = maxLine.text.length;
if (!options.lineWrapping)
doc.iter(from.line, to.line + 1, function(line) {
if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
});
if (from.line != to.line || lines.length > 1) gutterDirty = true;
var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
var lastHL = lst(lines);
// First adjust the line structure
if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") {
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
var added = [], prevLine = null;
for (var i = 0, e = lines.length - 1; i < e; ++i)
added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
lastLine.update(lastLine.text, hlSpans(lastHL));
if (nlines) doc.remove(from.line, nlines, callbacks);
if (added.length) doc.insert(from.line, added);
} else if (firstLine == lastLine) {
if (lines.length == 1) {
firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0]));
} else {
for (var added = [], i = 1, e = lines.length - 1; i < e; ++i)
added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL)));
firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
doc.insert(from.line + 1, added);
}
} else if (lines.length == 1) {
firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0]));
doc.remove(from.line + 1, nlines, callbacks);
} else {
var added = [];
firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL));
for (var i = 1, e = lines.length - 1; i < e; ++i)
added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
doc.insert(from.line + 1, added);
}
if (options.lineWrapping) {
var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
doc.iter(from.line, from.line + lines.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, from.line + lines.length, function(line) {
var l = line.text;
if (!line.hidden && l.length > maxLineLength) {
maxLine = line; maxLineLength = l.length; maxLineChanged = true;
recomputeMaxLength = false;
}
});
if (recomputeMaxLength) updateMaxLine = true;
}
// Adjust frontier, schedule worker
frontier = Math.min(frontier, from.line);
startWorker(400);
var lendiff = lines.length - nlines - 1;
// Remember that these lines changed, for updating the display
changes.push({from: from.line, to: to.line + 1, diff: lendiff});
if (options.onChange) {
// Normalize lines to contain only strings, since that's what
// the change event handler expects
for (var i = 0; i < lines.length; ++i)
if (typeof lines[i] != "string") lines[i] = lines[i].text;
var changeObj = {from: from, to: to, text: lines};
if (textChanged) {
for (var cur = textChanged; cur.next; cur = cur.next) {}
cur.next = changeObj;
} else textChanged = changeObj;
}
// Update the selection
function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
setSelection(clipPos(selFrom), clipPos(selTo),
updateLine(sel.from.line), updateLine(sel.to.line));
}
function needsScrollbar() {
var realHeight = doc.height * textHeight() + 2 * paddingTop();
return realHeight * .99 > scroller.offsetHeight ? realHeight : false;
}
function updateVerticalScroll(scrollTop) {
var scrollHeight = needsScrollbar();
scrollbar.style.display = scrollHeight ? "block" : "none";
if (scrollHeight) {
scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px";
scrollbar.style.height = scroller.clientHeight + "px";
if (scrollTop != null) {
scrollbar.scrollTop = scroller.scrollTop = scrollTop;
// 'Nudge' the scrollbar to work around a Webkit bug where,
// in some situations, we'd end up with a scrollbar that
// reported its scrollTop (and looked) as expected, but
// *behaved* as if it was still in a previous state (i.e.
// couldn't scroll up, even though it appeared to be at the
// bottom).
if (webkit) setTimeout(function() {
if (scrollbar.scrollTop != scrollTop) return;
scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1);
scrollbar.scrollTop = scrollTop;
}, 0);
}
} else {
sizer.style.minHeight = "";
}
// Position the mover div to align with the current virtual scroll position
mover.style.top = displayOffset * textHeight() + "px";
}
function computeMaxLength() {
maxLine = getLine(0); maxLineChanged = true;
var maxLineLength = maxLine.text.length;
doc.iter(1, doc.size, function(line) {
var l = line.text;
if (!line.hidden && l.length > maxLineLength) {
maxLineLength = l.length; maxLine = line;
}
});
updateMaxLine = false;
}
function replaceRange(code, from, to) {
from = clipPos(from);
if (!to) to = from; else to = clipPos(to);
code = splitLines(code);
function adjustPos(pos) {
if (posLess(pos, from)) return pos;
if (!posLess(to, pos)) return end;
var line = pos.line + code.length - (to.line - from.line) - 1;
var ch = pos.ch;
if (pos.line == to.line)
ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0));
return {line: line, ch: ch};
}
var end;
replaceRange1(code, from, to, function(end1) {
end = end1;
return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
});
return end;
}
function replaceSelection(code, collapse) {
replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
if (collapse == "end") return {from: end, to: end};
else if (collapse == "start") return {from: sel.from, to: sel.from};
else return {from: sel.from, to: end};
});
}
function replaceRange1(code, from, to, computeSel) {
var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length;
var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
updateLines(from, to, code, newSel.from, newSel.to);
}
function getRange(from, to, lineSep) {
var l1 = from.line, l2 = to.line;
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(lineSep || "\n");
}
function getSelection(lineSep) {
return getRange(sel.from, sel.to, lineSep);
}
function slowPoll() {
if (pollingFast) return;
poll.set(options.pollInterval, function() {
readInput();
if (focused) slowPoll();
});
}
function fastPoll() {
var missed = false;
pollingFast = true;
function p() {
var changed = readInput();
if (!changed && !missed) {missed = true; poll.set(60, p);}
else {pollingFast = false; slowPoll();}
}
poll.set(20, p);
}
// Previnput is a hack to work with IME. If we reset the textarea
// on every change, that breaks IME. So we look for changes
// compared to the previous content instead. (Modern browsers have
// events that indicate IME taking place, but these are not widely
// supported or compatible enough yet to rely on.)
var prevInput = "";
function readInput() {
if (!focused || hasSelection(input) || options.readOnly) return false;
var text = input.value;
if (text == prevInput) return false;
if (!nestedOperation) startOperation();
shiftSelecting = null;
var same = 0, l = Math.min(prevInput.length, text.length);
while (same < l && prevInput[same] == text[same]) ++same;
if (same < prevInput.length)
sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
else if (overwrite && posEq(sel.from, sel.to) && !pasteIncoming)
sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
replaceSelection(text.slice(same), "end");
if (text.length > 1000) { input.value = prevInput = ""; }
else prevInput = text;
if (!nestedOperation) endOperation();
pasteIncoming = false;
return true;
}
function resetInput(user) {
if (!posEq(sel.from, sel.to)) {
prevInput = "";
input.value = getSelection();
if (focused) selectInput(input);
} else if (user) prevInput = input.value = "";
}
function focusInput() {
if (options.readOnly != "nocursor") input.focus();
}
function scrollCursorIntoView() {
var coords = calculateCursorCoords();
scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
if (!focused) return;
var box = sizer.getBoundingClientRect(), doScroll = null;
if (coords.y + box.top < 0) doScroll = true;
else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
if (doScroll != null) {
var hidden = cursor.style.display == "none";
if (hidden) {
cursor.style.display = "";
cursor.style.left = coords.x + "px";
cursor.style.top = (coords.y - displayOffset) + "px";
}
cursor.scrollIntoView(doScroll);
if (hidden) cursor.style.display = "none";
}
}
function calculateCursorCoords() {
var cursor = localCoords(sel.inverted ? sel.from : sel.to);
var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
return {x: x, y: cursor.y, yBot: cursor.yBot};
}
function scrollIntoView(x1, y1, x2, y2) {
var scrollPos = calculateScrollPos(x1, y1, x2, y2);
if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;}
if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;}
}
function calculateScrollPos(x1, y1, x2, y2) {
var pl = paddingLeft(), pt = paddingTop();
y1 += pt; y2 += pt; x1 += pl; x2 += pl;
var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
var docBottom = needsScrollbar() || Infinity;
var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
var atLeft = x1 < gutterw + pl + 10;
if (x1 < screenleft + gutterw || atLeft) {
if (atLeft) x1 = 0;
result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
} else if (x2 > screenw + screenleft - 3) {
result.scrollLeft = x2 + 10 - screenw;
}
return result;
}
function visibleLines(scrollTop) {
var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
var fromHeight = Math.max(0, Math.floor(top / lh));
var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
return {from: lineAtHeight(doc, fromHeight),
to: lineAtHeight(doc, toHeight)};
}
// 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, suppressCallback, scrollTop) {
if (!scroller.clientWidth) {
showingFrom = showingTo = displayOffset = 0;
return;
}
// Compute the new visible window
// If scrollTop is specified, use that to determine which lines
// to render instead of the current scrollbar position.
var visible = visibleLines(scrollTop);
// Bail out if the visible area is already rendered and nothing changed.
if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
updateVerticalScroll(scrollTop);
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 && from == showingFrom && to == showingTo) {
updateVerticalScroll(scrollTop);
return;
}
intact.sort(function(a, b) {return a.domStart - b.domStart;});
var th = textHeight(), gutterDisplay = gutter.style.display;
lineDiv.style.display = "none";
patchDisplay(from, to, intact);
lineDiv.style.display = gutter.style.display = "";
var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
// This is just a bogus formula that detects when the editor is
// resized or the font size changes.
if (different) lastSizeC = scroller.clientHeight + th;
if (from != showingFrom || to != showingTo && options.onViewportChange)
setTimeout(function(){
if (options.onViewportChange) options.onViewportChange(instance, from, to);
});
showingFrom = from; showingTo = to;
displayOffset = heightAtLine(doc, from);
startWorker(100);
// 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);
function checkHeights() {
var curNode = lineDiv.firstChild, heightChanged = false;
doc.iter(showingFrom, showingTo, function(line) {
// Work around bizarro IE7 bug where, sometimes, our curNode
// is magically replaced with a new node in the DOM, leaving
// us with a reference to an orphan (nextSibling-less) node.
if (!curNode) return;
if (!line.hidden) {
var height = Math.round(curNode.offsetHeight / th) || 1;
if (line.height != height) {
updateLineHeight(line, height);
gutterDirty = heightChanged = true;
}
}
curNode = curNode.nextSibling;
});
return heightChanged;
}
if (options.lineWrapping) checkHeights();
gutter.style.display = gutterDisplay;
if (different || gutterDirty) {
// If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
}
updateVerticalScroll(scrollTop);
updateSelection();
if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
return true;
}
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 && 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});
if (change.to < range.to)
intact2.push({from: change.to + diff, to: range.to + diff,
domStart: range.domStart + (