./vendor/assets/javascripts/codemirror.js in codemirror-rails-0.1.1 vs ./vendor/assets/javascripts/codemirror.js in codemirror-rails-0.1.2

- old
+ new

@@ -72,21 +72,19 @@ var editing, bracketHighlighted; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. var maxLine = ""; - // 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); // Register our event handlers. connect(scroller, "mousedown", operation(onMouseDown)); // 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", operation(onContextMenu)); + if (!gecko) connect(scroller, "contextmenu", onContextMenu); connect(code, "dblclick", operation(onDblClick)); connect(scroller, "scroll", function() {updateDisplay([]); if (options.onScroll) options.onScroll(instance);}); connect(window, "resize", function() {updateDisplay(true);}); connect(input, "keyup", operation(onKeyUp)); connect(input, "keydown", operation(onKeyDown)); @@ -102,11 +100,11 @@ connect(input, "cut", function(){fastPoll();}); // 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) onFocus(); + if (hasFocus) setTimeout(onFocus, 20); else onBlur(); function isLine(l) {return l >= 0 && l < lines.length;} // The instance object that we'll return. Mostly calls out to // local functions in the CodeMirror function. Some do some extra @@ -116,11 +114,11 @@ var instance = { getValue: getValue, setValue: operation(setValue), getSelection: getSelection, replaceSelection: operation(replaceSelection), - focus: function(){focusInput(); onFocus(); prepareInput(); fastPoll();}, + focus: function(){focusInput(); onFocus(); fastPoll();}, setOption: function(option, value) { options[option] = value; if (option == "lineNumbers" || option == "gutter") gutterChanged(); else if (option == "mode" || option == "indentUnit") loadMode(); else if (option == "readOnly" && value == "nocursor") input.blur(); @@ -134,10 +132,14 @@ 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); }, charCoords: function(pos){return pageCoords(clipPos(pos));}, @@ -150,17 +152,26 @@ markText: operation(function(a, b, c){return operation(markText(a, b, c));}), setMarker: addGutterMarker, clearMarker: 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, where) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; code.appendChild(node); + node.style.left = left + "px"; + if (where == "over") top = pos.y; + else if (where == "fit") { + var vspace = lines.length * lineHeight(), hspace = code.clientWidth - paddingLeft(); + top = pos.y + node.offsetHeight > vspace ? vspace - node.offsetHeight : pos.y; + if (left + node.offsetWidth > hspace) left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + 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;}, getCursor: function(start) { if (start == null) start = sel.inverted; @@ -183,11 +194,12 @@ getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, 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;} }; function setValue(code) { history = null; var top = {line: 0, ch: 0}; @@ -210,23 +222,30 @@ if (options.onGutterClick) options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom); return e.stop(); } - if (gecko && e.button() == 3) onContextMenu(e); - if (e.button() != 1) return; + var start = posFromMouse(e); + switch (e.button()) { + 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() == scroller) e.stop(); return;} if (!focused) onFocus(); e.stop(); if (ld && +new Date - ld < 400) return selectLine(start.line); setCursor(start.line, start.ch, true); + var last = start, going; // And then we have to see if it's a drag event, in which case // the dragged-over text must be selected. function end() { focusInput(); updateInput = true; @@ -264,23 +283,24 @@ selectWordAt(pos); e.stop(); lastDoubleClick = +new Date; } function onDrop(e) { + e.e.preventDefault(); var pos = posFromMouse(e, true), files = e.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)); }; 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); @@ -352,23 +372,28 @@ else fastPoll(curKeyId); } function onFocus() { if (options.readOnly == "nocursor") return; - if (!focused && options.onFocus) options.onFocus(instance); - focused = true; + 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. // Afterwards, set the selection to selFrom, selTo. function updateLines(from, to, newText, selFrom, selTo) { @@ -377,12 +402,10 @@ for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text); history.addChange(from.line, newText.length, old); while (history.done.length > options.undoDepth) history.done.shift(); } updateLinesNoUndo(from, to, newText, selFrom, selTo); - if (newText.length < 5) - highlightLines(from.line, from.line + newText.length) } function unredoHelper(from, to) { var change = from.pop(); if (change) { var replaced = [], end = change.start + change.added; @@ -452,11 +475,16 @@ for (var i = 0, l = work.length; i < l; ++i) { var task = work[i]; 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 = {from: from, to: to, text: newText}; @@ -733,10 +761,12 @@ updateGutter(); } var textWidth = stringWidth(maxLine); lineSpace.style.width = textWidth > scroller.clientWidth ? textWidth + "px" : ""; + // Needed to prevent odd wrapping/hiding of widgets placed in here. + code.style.width = (lineSpace.offsetWidth + lineSpace.offsetLeft) + "px"; // Since this is all rather error prone, it is honoured with the // only assertion in the whole file. if (lineDiv.childNodes.length != showingTo - showingFrom) throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) + @@ -925,25 +955,30 @@ replaceSelection("\n", "end"); if (options.enterMode != "flat") 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 ? 1 : 0); + 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)) { if (shift) indentLine(sel.from.line, "smart"); else replaceSelection("\t", "end"); 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; } @@ -1120,11 +1155,13 @@ } function paddingTop() {return lineSpace.offsetTop;} function paddingLeft() {return lineSpace.offsetLeft;} function posFromMouse(e, liberal) { - var offW = eltOffset(scroller, true), x = e.e.clientX, y = e.e.clientY; + var offW = eltOffset(scroller, true), x, y; + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.e.clientX; y = e.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; @@ -1134,30 +1171,33 @@ } 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; + inputDiv.style.position = "absolute"; 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; opacity: .05;"; + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + leaveInputAlone = true; var val = input.value = getSelection(); focusInput(); setSelRange(input, 0, input.value.length); - leaveInputAlone = true; function rehide() { - if (input.value != val) operation(replaceSelection)(input.value, "end"); + 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(); } if (gecko) { - e.stop() + e.stop(); var mouseup = connect(window, "mouseup", function() { mouseup(); setTimeout(rehide, 20); }, true); } @@ -1200,23 +1240,24 @@ else if (!stack.length) return {pos: pos, match: true}; } } } } - for (var i = head.line, e = forward ? Math.min(i + 50, lines.length) : Math.max(-1, 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) + : function() {}; + var clear = operation(function(){one(); two();}); + if (autoclear) setTimeout(clear, 800); + else bracketHighlighted = clear; } // Finds the line to start with when starting a parse. Tries to // find a line with a stateAfter, so that it can start with a // valid state. If that fails, it returns the line with the @@ -1265,23 +1306,27 @@ if (task >= lines.length) continue; var start = findStartLine(task), state = start && lines[start-1].stateAfter; if (state) state = copyState(mode, state); else state = startState(mode); - var unchanged = 0; + var unchanged = 0, compare = mode.compareStates; 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}); return; } var changed = line.highlight(mode, state); line.stateAfter = copyState(mode, state); - if (changed || !hadState) unchanged = 0; - else if (++unchanged > 3) break; + if (compare) { + if (hadState && compare(hadState, state)) break; + } else { + if (changed || !hadState) unchanged = 0; + else if (++unchanged > 3) break; + } } changes.push({from: task, to: i}); } if (foundWork && options.onHighlightComplete) options.onHighlightComplete(instance); @@ -1306,11 +1351,12 @@ if (reScroll) scrollCursorIntoView(); if (selectionChanged) 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.matchBrackets) setTimeout(operation(function() { if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} @@ -1433,11 +1479,19 @@ } } }, 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)) @@ -1493,11 +1547,11 @@ 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 || {}); - } + }; CodeMirror.listModes = function() { var list = []; for (var m in modes) if (modes.propertyIsEnumerable(m)) list.push(m); return list; @@ -1928,21 +1982,25 @@ else ++n; } 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. // If screen==true, it returns screen (rather than page) coordinates. function eltOffset(node, screen) { var doc = node.ownerDocument.body; - var x = 0, y = 0, hitDoc = false; + var x = 0, y = 0, skipDoc = false; for (var n = node; n; n = n.offsetParent) { x += n.offsetLeft; y += n.offsetTop; - // Fixed-position elements don't have the document in their offset chain - if (n == doc) hitDoc = true; + if (screen && computedStyle(n).position == "fixed") + skipDoc = true; } - var e = screen && hitDoc ? null : doc; + 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. @@ -1979,54 +2037,55 @@ return -1; } // 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)); pos = nl + 1; } result.push(string.slice(pos)); 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;} }; 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. - var setSelRange = function(te, start, end) { + 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 - var setSelRange = function(te, start, end) { + 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) { + 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(); localRange.moveToBookmark(range.getBookmark()); @@ -2044,10 +2103,10 @@ var end = -localRange.moveEnd("character", -len); 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(); var newlines = 0, txt = te.value; for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1))