+/* In-browser code editing + made bearable */ ++ +
CodeMirror is a JavaScript component that + provides a code editor in the browser. When a mode is available for + the language you are coding in, it will color your code, and + optionally help with indentation.
+ +A rich programming API and a CSS + theming system are available for customizing CodeMirror to fit your + application, and extending it with new functionality.
+ +All of CodeMirror is released under a MIT-style license. To get it, you can download + the latest + release or the current development + snapshot as zip files. To create a custom minified script file, + you can use the compression API.
+ +We use git for version control. + The main repository can be fetched in this way:
+ +git clone http://marijnhaverbeke.nl/git/codemirror2+ +
CodeMirror can also be found on GitHub at marijnh/CodeMirror2. + If you plan to hack on the code and contribute patches, the best way + to do it is to create a GitHub fork, and send pull requests.
+ +The manual is your first stop for + learning how to use this library. It starts with a quick explanation + of how to use the editor, and then describes the API in detail.
+ +For those who want to learn more about the code, there is + an overview of the internals available. + The source code + itself is, for the most part, also well commented.
+ +There is + a Google + group (a sort of mailing list/newsgroup thing) for discussion + and news related to CodeMirror. When reporting a bug, + read this first. If you have + a github account, + simply open + an issue there. Otherwise, post something to + the group, + or e-mail me directly: Marijn + Haverbeke.
+ +The following browsers are able to run CodeMirror:
+ +<!doctype
+ html>
is recommended.)I am not actively testing against every new browser release, and + vendors have a habit of introducing bugs all the time, so I am + relying on the community to tell me when something breaks. + See here for information on how to contact + me.
+ +CodeMirror is developed and maintained by me, Marijn Haverbeke, + in my own time. If your company is getting value out of CodeMirror, + please consider purchasing a support contract.
+ +CodeMirror support contracts exist in two + forms—basic at €100 per month, + and premium at €500 per + month. Contact me for further + information.
+ +23-07-2012: Version 2.32:
+ +Emergency fix for a bug where an editor with + line wrapping on IE will break when there is no + scrollbar.
+ +20-07-2012: Version 2.31:
+ +setSize
method for programmatic resizing.getHistory
and setHistory
methods.getValue
and getRange
.22-06-2012: Version 2.3:
+ +getScrollInfo
method.23-05-2012: Version 2.25:
+ +23-04-2012: Version 2.24:
+ +dragDrop
+ and onDragEvent
+ options.compoundChange
API method.catchall
in key maps,
+ add nofallthrough
boolean field instead.26-03-2012: Version 2.23:
+ +setLineClass
.charCoords
+ and cursorCoords
with a mode
argument.autofocus
option.findMarksAt
method.27-02-2012: Version 2.22:
+ +autoClearEmptyLines
option.27-01-2012: Version 2.21:
+ +smartIndent
+ option.readOnly
-mode.scrollTo
method.20-12-2011: Version 2.2:
+ +coordsFromIndex
+ to posFromIndex
,
+ add indexFromPos
+ method.21-11-2011: Version 2.18:
+Fixes TextMarker.clear
, which is broken in 2.17.
21-11-2011: Version 2.17:
+setBookmark
method.lib/util
.27-10-2011: Version 2.16:
+coordsFromIndex
method.setValue
now no longer clears history. Use clearHistory
for that.markText
now
+ returns an object with clear
and find
+ methods. Marked text is now more robust when edited.' + // Absolutely positioned blinky cursor + ' ' + // Used to force a width '' + // DIVs containing the selection and the actual code '
' + line.getHTML(tabText) + ''; + var html = '
' + + line.getHTML(makeTab) + ''; // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.className) - html = '
' + html + "
' : ""), text); for (var j = 1; j < line.height; ++j) html.push(""); + if (!marker) normalNode = i; } ++i; }); gutter.style.display = "none"; gutterText.innerHTML = html.join(""); - var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; - while (val.length + pad.length < minwidth) pad += "\u00a0"; - if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null && options.lineNumbers) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; lineSpace.style.marginLeft = gutter.offsetWidth + "px"; gutterDirty = false; + return resized; } function updateSelection() { var collapsed = posEq(sel.from, sel.to); @@ -1031,20 +1273,24 @@ var CodeMirror = (function() { selectionDiv.style.display = "none"; } else { var sameLine = fromPos.y == toPos.y, html = ""; + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; function add(left, top, right, height) { + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" + : "right: " + right + "px"; html += ''; + 'px; top: ' + top + 'px; ' + rstyle + '; height: ' + height + 'px">'; } if (sel.from.ch && fromPos.y >= 0) { - var right = sameLine ? lineSpace.clientWidth - toPos.x : 0; + var right = sameLine ? clientWidth - toPos.x : 0; add(fromPos.x, fromPos.y, right, th); } var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); - var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart; + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; if (middleHeight > 0.2 * th) add(0, middleStart, 0, middleHeight); - if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th) - add(0, toPos.y, lineSpace.clientWidth - toPos.x, th); + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); selectionDiv.innerHTML = html; cursor.style.display = "none"; selectionDiv.style.display = ""; @@ -1074,13 +1320,32 @@ var CodeMirror = (function() { if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} // Skip over hidden lines. - if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch); + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); if (posEq(from, to)) sel.inverted = false; else if (posEq(from, sel.to)) sel.inverted = false; else if (posEq(to, sel.from)) sel.inverted = true; + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function() { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); + } + }, 10)); + } + } + sel.from = from; sel.to = to; selectionChanged = true; } @@ -1091,13 +1356,14 @@ var CodeMirror = (function() { var line = getLine(lNo); if (!line.hidden) { var ch = pos.ch; - if (ch > oldCh || ch > line.text.length) ch = line.text.length; + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; return {line: lNo, ch: ch}; } lNo += dir; } } var line = getLine(pos.line); + var toEnd = pos.ch == line.text.length && pos.ch != oldCh; if (!line.hidden) return pos; if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); else return getNonHidden(-1) || getNonHidden(1); @@ -1164,19 +1430,22 @@ var CodeMirror = (function() { if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); else if (unit == "line") dist = textHeight(); var target = coordsChar(pos.x, pos.y + dist * dir + 2); + if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; setCursor(target.line, target.ch, true); goalColumn = pos.x; } - function selectWordAt(pos) { + function findWordAt(pos) { var line = getLine(pos.line).text; var start = pos.ch, end = pos.ch; - while (start > 0 && isWordChar(line.charAt(start - 1))) --start; - while (end < line.length && isWordChar(line.charAt(end))) ++end; - setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); + var check = isWordChar(line.charAt(start < line.length ? start : start - 1)) ? + isWordChar : function(ch) {return !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; } function selectLine(line) { - setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); } function indentSelected(mode) { if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); @@ -1193,26 +1462,23 @@ var CodeMirror = (function() { var line = getLine(n), curSpace = line.indentation(options.tabSize), curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "smart") { + indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) how = "prev"; + } if (how == "prev") { if (n) indentation = getLine(n-1).indentation(options.tabSize); else indentation = 0; } - else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); else if (how == "add") indentation = curSpace + options.indentUnit; else if (how == "subtract") indentation = curSpace - options.indentUnit; indentation = Math.max(0, indentation); var diff = indentation - curSpace; - if (!diff) { - if (sel.from.line != n && sel.to.line != n) return; - var indentString = curSpaceString; - } - else { - var indentString = "", pos = 0; - if (options.indentWithTabs) - for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} - while (pos < indentation) {++pos; indentString += " ";} - } + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + while (pos < indentation) {++pos; indentString += " ";} replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); } @@ -1239,9 +1505,10 @@ var CodeMirror = (function() { if (guess != 1) updateLineHeight(line, guess); }); lineSpace.style.width = code.style.width = ""; + widthForcer.style.left = ""; } else { wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); - maxWidth = null; maxLine = ""; + maxLine = ""; maxLineChanged = true; doc.iter(0, doc.size, function(line) { if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); if (line.text.length > maxLine.length) maxLine = line.text; @@ -1249,18 +1516,21 @@ var CodeMirror = (function() { } changes.push({from: 0, to: doc.size}); } - function computeTabText() { - for (var str = '', i = 0; i < options.tabSize; ++i) str += " "; - return str + ""; - } - function tabsChanged() { - tabText = computeTabText(); - updateDisplay(true); + function makeTab(col) { + var w = options.tabSize - col % options.tabSize, cached = tabCache[w]; + if (cached) return cached; + for (var str = '', i = 0; i < w; ++i) str += " "; + return (tabCache[w] = {html: str + "", width: w}); } function themeChanged() { - scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); } + function keyMapChanged() { + var style = keyMap[options.keyMap].style; + wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } function TextMarker() { this.set = []; } TextMarker.prototype.clear = operation(function() { @@ -1271,7 +1541,7 @@ var CodeMirror = (function() { var lineN = lineNo(line); min = Math.min(min, lineN); max = Math.max(max, lineN); for (var j = 0; j < mk.length; ++j) - if (mk[j].set == this.set) mk.splice(j--, 1); + if (mk[j].marker == this) mk.splice(j--, 1); } if (min != Infinity) changes.push({from: min, to: max + 1}); @@ -1282,7 +1552,7 @@ var CodeMirror = (function() { 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.marker == this) { if (mark.from != null || mark.to != null) { var found = lineNo(line); if (found != null) { @@ -1299,8 +1569,9 @@ var CodeMirror = (function() { function markText(from, to, className) { from = clipPos(from); to = clipPos(to); var tm = new TextMarker(); + if (!posLess(from, to)) return tm; function add(line, from, to, className) { - getLine(line).addMark(new MarkedText(from, to, className, tm.set)); + getLine(line).addMark(new MarkedText(from, to, className, tm)); } if (from.line == to.line) add(from.line, from.ch, to.ch, className); else { @@ -1320,6 +1591,19 @@ var CodeMirror = (function() { return bm; } + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], marked = getLine(pos.line).marked; + if (!marked) return markers; + for (var i = 0, e = marked.length; i < e; ++i) { + var m = marked[i]; + if ((m.from == null || m.from <= pos.ch) && + (m.to == null || m.to >= pos.ch)) + markers.push(m.marker || m); + } + return markers; + } + function addGutterMarker(line, text, className) { if (typeof line == "number") line = getLine(clipLine(line)); line.gutterMarker = {text: text, style: className}; @@ -1341,10 +1625,11 @@ var CodeMirror = (function() { else return null; return line; } - function setLineClass(handle, className) { + function setLineClass(handle, className, bgClassName) { return changeLine(handle, function(line) { - if (line.className != className) { + if (line.className != className || line.bgClassName != bgClassName) { line.className = className; + line.bgClassName = bgClassName; return true; } }); @@ -1353,11 +1638,21 @@ var CodeMirror = (function() { return changeLine(handle, function(line, no) { if (line.hidden != hidden) { line.hidden = hidden; + if (!options.lineWrapping) { + var l = line.text; + if (hidden && l.length == maxLine.length) { + updateMaxLine = true; + } else if (!hidden && l.length > maxLine.length) { + maxLine = l; updateMaxLine = false; + } + } updateLineHeight(line, hidden ? 0 : 1); var fline = sel.from.line, tline = sel.to.line; if (hidden && (fline == no || tline == no)) { var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; setSelection(from, to); } return (gutterDirty = true); @@ -1371,14 +1666,13 @@ var CodeMirror = (function() { var n = line; line = getLine(line); if (!line) return null; - } - else { + } else { var n = lineNo(line); if (n == null) return null; } var marker = line.gutterMarker; return {line: n, handle: line, text: line.text, markerText: marker && marker.text, - markerClass: marker && marker.style, lineClass: line.className}; + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; } function stringWidth(str) { @@ -1392,8 +1686,7 @@ var CodeMirror = (function() { if (x <= 0) return 0; var lineObj = getLine(line), text = lineObj.text; function getX(len) { - measure.innerHTML = "
"); html.push("" + lineObj.getHTML(tabText, len) + "
"; - return measure.firstChild.firstChild.offsetWidth; + return measureLine(lineObj, len).left; } var from = 0, fromX = 0, to = text.length, toX; // Guess a suitable upper bound for our search. @@ -1416,19 +1709,13 @@ var CodeMirror = (function() { } } - var tempId = Math.floor(Math.random() * 0xffffff).toString(16); + var tempId = "CodeMirror-temp-" + Math.floor(Math.random() * 0xffffff).toString(16); function measureLine(line, ch) { if (ch == 0) return {top: 0, left: 0}; - var extra = ""; - // Include extra text at the end to make sure the measured line is wrapped in the right way. - if (options.lineWrapping) { - var end = line.text.indexOf(" ", ch + 2); - extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); - } - measure.innerHTML = "" + line.getHTML(tabText, ch) + - '' + htmlEscape(line.text.charAt(ch) || " ") + "" + - extra + "
"; - var elt = document.getElementById("CodeMirror-temp-" + tempId); + var wbr = options.lineWrapping && ch < line.text.length && + spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1)); + measure.innerHTML = "" + line.getHTML(makeTab, ch, tempId, wbr) + ""; + var elt = document.getElementById(tempId); var top = elt.offsetTop, left = elt.offsetLeft; // Older IEs report zero offsets for spans directly after a wrap if (ie && top == 0 && left == 0) { @@ -1528,8 +1815,8 @@ var CodeMirror = (function() { return coordsChar(x - offL.left, y - offL.top); } function onContextMenu(e) { - var pos = posFromMouse(e); - if (!pos || window.opera) return; // Opera is difficult. + var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; + if (!pos || opera) return; // Opera is difficult. if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) operation(setCursor)(pos.line, pos.ch); @@ -1544,9 +1831,10 @@ var CodeMirror = (function() { selectInput(input); function rehide() { var newVal = splitLines(input.value).join("\n"); - if (newVal != val) operation(replaceSelection)(newVal, "end"); + if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end"); inputDiv.style.position = "relative"; input.style.cssText = oldCSS; + if (ie_lt9) scrollbar.scrollTop = scrollPos; leaveInputAlone = false; resetInput(true); slowPoll(); @@ -1558,8 +1846,7 @@ var CodeMirror = (function() { mouseup(); setTimeout(rehide, 20); }, true); - } - else { + } else { setTimeout(rehide, 50); } } @@ -1589,7 +1876,7 @@ var CodeMirror = (function() { var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { var text = st[i]; - if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + if (st[i+1] != style) {pos += d * text.length; continue;} for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { var match = matching[cur]; @@ -1676,13 +1963,17 @@ var CodeMirror = (function() { var changed = line.highlight(mode, state, options.tabSize); if (changed) realChange = true; line.stateAfter = copyState(mode, state); + var done = null; if (compare) { - if (hadState && compare(hadState, state)) return true; - } else { + var same = hadState && compare(hadState, state); + if (same != Pass) done = !!same; + } + if (done == null) { if (changed !== false || !hadState) unchanged = 0; else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) - return true; + done = true; } + if (done) return true; ++i; }); if (bail) return; @@ -1705,14 +1996,24 @@ var CodeMirror = (function() { changes = []; selectionChanged = false; callbacks = []; } function endOperation() { - var reScroll = false, updated; - if (selectionChanged) reScroll = !scrollCursorIntoView(); - if (changes.length) updated = updateDisplay(changes, true); + if (updateMaxLine) computeMaxLength(); + if (maxLineChanged && !options.lineWrapping) { + var cursorWidth = widthForcer.offsetWidth, left = stringWidth(maxLine); + widthForcer.style.left = left + "px"; + lineSpace.style.minWidth = (left + cursorWidth) + "px"; + maxLineChanged = false; + } + var newScrollPos, updated; + if (selectionChanged) { + var coords = calculateCursorCoords(); + newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); + } + if (changes.length) updated = updateDisplay(changes, true, (newScrollPos ? newScrollPos.scrollTop : null)); else { if (selectionChanged) updateSelection(); if (gutterDirty) updateGutter(); } - if (reScroll) scrollCursorIntoView(); + if (newScrollPos) scrollCursorIntoView(); if (selectionChanged) {scrollEditorIntoView(); restartBlink();} if (focused && !leaveInputAlone && @@ -1724,11 +2025,11 @@ var CodeMirror = (function() { if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} if (posEq(sel.from, sel.to)) matchBrackets(false); }), 20); - var tc = textChanged, cbs = callbacks; // these can be reset by callbacks - if (selectionChanged && options.onCursorActivity) + var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks + if (textChanged && options.onChange && instance) + options.onChange(instance, textChanged); + if (sc && options.onCursorActivity) options.onCursorActivity(instance); - if (tc && options.onChange && instance) - options.onChange(instance, tc); for (var i = 0; i < cbs.length; ++i) cbs[i](instance); if (updated && options.onUpdate) options.onUpdate(instance); } @@ -1742,6 +2043,11 @@ var CodeMirror = (function() { }; } + function compoundChange(f) { + history.startCompound(); + try { return f(); } finally { history.endCompound(); } + } + for (var ext in extensions) if (extensions.propertyIsEnumerable(ext) && !instance.propertyIsEnumerable(ext)) @@ -1761,13 +2067,16 @@ var CodeMirror = (function() { keyMap: "default", extraKeys: null, electricChars: true, + autoClearEmptyLines: false, onKeyEvent: null, + onDragEvent: null, lineWrapping: false, lineNumbers: false, gutter: false, fixedGutter: false, firstLineNumber: 1, readOnly: false, + dragDrop: true, onChange: null, onCursorActivity: null, onGutterClick: null, @@ -1780,7 +2089,8 @@ var CodeMirror = (function() { pollInterval: 100, undoDepth: 40, tabindex: null, - document: window.document + autofocus: null, + lineNumberFormatter: function(integer) { return integer; } }; var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); @@ -1788,27 +2098,31 @@ var CodeMirror = (function() { var win = /Win/.test(navigator.platform); // Known modes, by name and by MIME - var modes = {}, mimeModes = {}; + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; CodeMirror.defineMode = function(name, mode) { if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } modes[name] = mode; }; CodeMirror.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; - CodeMirror.getMode = function(options, spec) { + CodeMirror.resolveMode = function(spec) { if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - 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 || {}); + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + return CodeMirror.resolveMode("application/xml"); + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + return mfactory(options, spec); }; CodeMirror.listModes = function() { var list = []; @@ -1865,6 +2179,10 @@ var CodeMirror = (function() { indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, insertTab: function(cm) {cm.replaceSelection("\t", "end");}, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end"); + }, transposeChars: function(cm) { var cur = cm.getCursor(), line = cm.getLine(cur.line); if (cur.ch > 0 && cur.ch < line.length - 1) @@ -1882,7 +2200,7 @@ var CodeMirror = (function() { keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" }; // Note that the save and find-related commands aren't defined by @@ -1893,6 +2211,7 @@ var CodeMirror = (function() { "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", fallthrough: "basic" }; keyMap.macDefault = { @@ -1901,6 +2220,7 @@ var CodeMirror = (function() { "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", fallthrough: ["basic", "emacsy"] }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; @@ -1911,20 +2231,30 @@ var CodeMirror = (function() { "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" }; - function lookupKey(name, extraMap, map) { - function lookup(name, map, ft) { + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + function lookupKey(name, extraMap, map, handle, stop) { + function lookup(map) { + map = getKeyMap(map); var found = map[name]; - if (found != null) return found; - if (ft == null) ft = map.fallthrough; - if (ft == null) return map.catchall; - if (typeof ft == "string") return lookup(name, keyMap[ft]); - for (var i = 0, e = ft.length; i < e; ++i) { - found = lookup(name, keyMap[ft[i]]); - if (found != null) return found; + if (found != null && handle(found)) return true; + if (map.nofallthrough) { + if (stop) stop(); + return true; } - return null; + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + if (lookup(fallthrough[i])) return true; + } + return false; } - return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]); + if (extraMap && lookup(extraMap)) return true; + return lookup(map); } function isModifierKey(event) { var name = keyNames[e_prop(event, "keyCode")]; @@ -1936,6 +2266,8 @@ var CodeMirror = (function() { options.value = textarea.value; if (!options.tabindex && textarea.tabindex) options.tabindex = textarea.tabindex; + if (options.autofocus == null && textarea.getAttribute("autofocus") != null) + options.autofocus = true; function save() {textarea.value = instance.getValue();} if (textarea.form) { @@ -2036,8 +2368,7 @@ var CodeMirror = (function() { if (consume !== false) this.pos += pattern.length; return true; } - } - else { + } else { var match = this.string.slice(this.pos).match(pattern); if (match && consume !== false) this.pos += match[0].length; return match; @@ -2047,34 +2378,34 @@ var CodeMirror = (function() { }; CodeMirror.StringStream = StringStream; - function MarkedText(from, to, className, set) { - this.from = from; this.to = to; this.style = className; this.set = set; + function MarkedText(from, to, className, marker) { + this.from = from; this.to = to; this.style = className; this.marker = marker; } MarkedText.prototype = { - attach: function(line) { this.set.push(line); }, + attach: function(line) { this.marker.set.push(line); }, detach: function(line) { - var ix = indexOf(this.set, line); - if (ix > -1) this.set.splice(ix, 1); + var ix = indexOf(this.marker.set, line); + if (ix > -1) this.marker.set.splice(ix, 1); }, split: function(pos, lenBefore) { if (this.to <= pos && this.to != null) return null; var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; var to = this.to == null ? null : this.to - pos + lenBefore; - return new MarkedText(from, to, this.style, this.set); + return new MarkedText(from, to, this.style, this.marker); }, - dup: function() { return new MarkedText(null, null, this.style, this.set); }, + dup: function() { return new MarkedText(null, null, this.style, this.marker); }, clipTo: function(fromOpen, from, toOpen, to, diff) { - if (this.from != null && this.from >= from) - this.from = Math.max(to, this.from) + diff; - if (this.to != null && this.to > from) - this.to = to < this.to ? this.to + diff : from; if (fromOpen && to > this.from && (to < this.to || this.to == null)) this.from = null; + else if (this.from != null && this.from >= from) + this.from = Math.max(to, this.from) + diff; if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) this.to = null; + else if (this.to != null && this.to > from) + this.to = to < this.to ? this.to + diff : from; }, isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, - sameSet: function(x) { return this.set == x.set; } + sameSet: function(x) { return this.marker == x.marker; } }; function Bookmark(pos) { @@ -2117,7 +2448,7 @@ var CodeMirror = (function() { this.styles = styles || [text, null]; this.text = text; this.height = 1; - this.marked = this.gutterMarker = this.className = this.handlers = null; + this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null; this.stateAfter = this.parent = this.hidden = null; } Line.inheritMarks = function(text, orig) { @@ -2163,6 +2494,7 @@ var CodeMirror = (function() { if (newmark) { if (!taken.marked) taken.marked = []; taken.marked.push(newmark); newmark.attach(taken); + if (newmark == mark) mk.splice(i--, 1); } } } @@ -2203,11 +2535,20 @@ var CodeMirror = (function() { fixMarkEnds: function(other) { var mk = this.marked, omk = other.marked; if (!mk) return; - for (var i = 0; i < mk.length; ++i) { + outer: for (var i = 0; i < mk.length; ++i) { var mark = mk[i], close = mark.to == null; if (close && omk) { - for (var j = 0; j < omk.length; ++j) - if (omk[j].sameSet(mark)) {close = false; break;} + for (var j = 0; j < omk.length; ++j) { + var om = omk[j]; + if (!om.sameSet(mark) || om.from != null) continue + if (mark.from == this.text.length && om.to == 0) { + omk.splice(j, 1); + mk.splice(i--, 1); + continue outer; + } else { + close = false; break; + } + } } if (close) mark.to = this.text.length; } @@ -2272,34 +2613,87 @@ var CodeMirror = (function() { indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, // Produces an HTML fragment for the line, taking selection, // marking, and highlighting into account. - getHTML: function(tabText, endAt) { - var html = [], first = true; - function span(text, style) { + getHTML: function(makeTab, wrapAt, wrapId, wrapWBR) { + var html = [], first = true, col = 0; + function span_(text, style) { if (!text) return; // Work around a bug where, in some compat modes, IE ignores leading spaces if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); first = false; - if (style) html.push('', htmlEscape(text).replace(/\t/g, tabText), ""); - else html.push(htmlEscape(text).replace(/\t/g, tabText)); + if (text.indexOf("\t") == -1) { + col += text.length; + var escaped = htmlEscape(text); + } else { + var escaped = ""; + for (var pos = 0;;) { + var idx = text.indexOf("\t", pos); + if (idx == -1) { + escaped += htmlEscape(text.slice(pos)); + col += text.length - pos; + break; + } else { + col += idx - pos; + var tab = makeTab(col); + escaped += htmlEscape(text.slice(pos, idx)) + tab.html; + col += tab.width; + pos = idx + 1; + } + } + } + if (style) html.push('', escaped, ""); + else html.push(escaped); + } + var span = span_; + if (wrapAt != null) { + var outPos = 0, open = ""; + span = function(text, style) { + var l = text.length; + if (wrapAt >= outPos && wrapAt < outPos + l) { + if (wrapAt > outPos) { + span_(text.slice(0, wrapAt - outPos), style); + // See comment at the definition of spanAffectsWrapping + if (wrapWBR) html.push(""); + } + html.push(open); + var cut = wrapAt - outPos; + span_(opera ? text.slice(cut, cut + 1) : text.slice(cut), style); + html.push(" "); + if (opera) span_(text.slice(cut + 1), style); + wrapAt--; + outPos += l; + } else { + outPos += l; + span_(text, style); + // Output empty wrapper when at end of line + // (Gecko and IE8+ do strange wrapping when adding a space + // to the end of the line. Other browsers don't react well + // to zero-width spaces. So we do hideous browser sniffing + // to determine which to use.) + if (outPos == wrapAt && outPos == len) + html.push(open + (gecko || (ie && !ie_lt8) ? "" : " ") + ""); + // Stop outputting HTML when gone sufficiently far beyond measure + else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; + } + } } + var st = this.styles, allText = this.text, marked = this.marked; var len = allText.length; - if (endAt != null) len = Math.min(endAt, len); function styleToClass(style) { if (!style) return null; return "cm-" + style.replace(/ +/g, " cm-"); } - if (!allText && endAt == null) + if (!allText && wrapAt == null) { span(" "); - else if (!marked || !marked.length) + } else if (!marked || !marked.length) { 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, styleToClass(style)); } - else { + } else { var pos = 0, i = 0, text = "", style, sg = 0; var nextChange = marked[0].from || 0, marks = [], markpos = 0; function advanceMarks() { @@ -2311,7 +2705,8 @@ var CodeMirror = (function() { } nextChange = markpos < marked.length ? marked[markpos].from : Infinity; for (var i = 0; i < marks.length; ++i) { - var to = marks[i].to || Infinity; + var to = marks[i].to; + if (to == null) to = Infinity; if (to == pos) marks.splice(i--, 1); else nextChange = Math.min(to, nextChange); } @@ -2349,8 +2744,7 @@ var CodeMirror = (function() { if (state == 0) { if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); if (end >= from) state = 1; - } - else if (state == 1) { + } else if (state == 1) { if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); else dest.push(part, source[i+1]); } @@ -2385,7 +2779,7 @@ var CodeMirror = (function() { }, insertHeight: function(at, lines, height) { this.height += height; - this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; }, iterN: function(at, n, op) { @@ -2551,33 +2945,36 @@ var CodeMirror = (function() { function History() { this.time = 0; this.done = []; this.undone = []; + this.compound = 0; + this.closed = false; } History.prototype = { addChange: function(start, added, old) { this.undone.length = 0; var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; var dtime = time - this.time; - if (dtime > 400 || !last) { - this.done.push([{start: start, added: added, old: old}]); - } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) { + + if (this.compound && cur && !this.closed) { cur.push({start: start, added: added, old: old}); + } else if (dtime > 400 || !last || this.closed || + last.start > start + old.length || last.start + last.added < start) { + this.done.push([{start: start, added: added, old: old}]); + this.closed = false; } else { - var oldoff = 0; - if (start < last.start) { - for (var i = last.start - start - 1; i >= 0; --i) - last.old.unshift(old[i]); - last.added += last.start - start; - last.start = start; - } - else if (last.start < start) { - oldoff = start - last.start; - added += oldoff; - } - for (var i = last.added - oldoff, e = old.length; i < e; ++i) - last.old.push(old[i]); - if (last.added < added) last.added = added; + var startBefore = Math.max(0, last.start - start), + endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); + for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); + for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); + if (startBefore) last.start = start; + last.added += added - (old.length - startBefore - endAfter); } this.time = time; + }, + startCompound: function() { + if (!this.compound++) this.closed = true; + }, + endCompound: function() { + if (!--this.compound) this.closed = true; } }; @@ -2603,10 +3000,14 @@ var CodeMirror = (function() { 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; + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; } // Allow 3rd-party code to override event properties by adding an override @@ -2622,8 +3023,7 @@ var CodeMirror = (function() { if (typeof node.addEventListener == "function") { node.addEventListener(type, handler, false); if (disconnect) return function() {node.removeEventListener(type, handler, false);}; - } - else { + } else { var wrapHandler = function(event) {handler(event || window.event);}; node.attachEvent("on" + type, wrapHandler); if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; @@ -2634,26 +3034,48 @@ var CodeMirror = (function() { function Delayed() {this.id = null;} Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; - // Detect drag-and-drop - var dragAndDrop = function() { - // IE8 has ondragstart and ondrop properties, but doesn't seem to - // actually support ondragstart the way it's supposed to work. - if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; - var div = document.createElement('div'); - return "draggable" in div; - }(); + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var quirksMode = ie && document.documentMode == 5; var webkit = /WebKit\//.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var opera = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = document.createElement('div'); + return "draggable" in div || "dragDrop" in div; + }(); - var lineSep = "\n"; // Feature-detect whether newlines in textareas are converted to \r\n - (function () { + var lineSep = function () { var te = document.createElement("textarea"); te.value = "foo\nbar"; - if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; - }()); + if (te.value.indexOf("\r") > -1) return "\r\n"; + return "\n"; + }(); + + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of regexps matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + var spanAffectsWrapping = /^$/; // Won't match any two-character string + if (gecko) spanAffectsWrapping = /$'/; + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. @@ -2674,26 +3096,7 @@ var CodeMirror = (function() { 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 bod = node.ownerDocument.body; - var x = 0, y = 0, skipBody = false; - for (var n = node; n; n = n.offsetParent) { - var ol = n.offsetLeft, ot = n.offsetTop; - // Firefox reports weird inverted offsets when the body has a border. - if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } - else { x += ol, y += ot; } - if (screen && computedStyle(n).position == "fixed") - skipBody = true; - } - var e = screen && !skipBody ? null : bod; - for (var n = node.parentNode; n != e; n = n.parentNode) - if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} - return {left: x, top: y}; - } - // Use the faster and saner getBoundingClientRect method when possible. - if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } @@ -2709,7 +3112,7 @@ var CodeMirror = (function() { } } return box; - }; + } // Get a node's text content. function eltText(node) { @@ -2734,18 +3137,19 @@ var CodeMirror = (function() { } // Recent (late 2011) Opera betas insert bogus newlines at the start // of the textContent, so we strip those. - if (htmlEscape("a") == "\na") + if (htmlEscape("a") == "\na") { htmlEscape = function(str) { escapeElement.textContent = str; return escapeElement.innerHTML.slice(1); }; // Some IEs don't preserve tabs through innerHTML - else if (htmlEscape("\t") != "\t") + } else if (htmlEscape("\t") != "\t") { htmlEscape = function(str) { escapeElement.innerHTML = ""; escapeElement.appendChild(document.createTextNode(str)); return escapeElement.innerHTML; }; + } CodeMirror.htmlEscape = htmlEscape; // Used to position the cursor after an undo/redo by finding the @@ -2771,14 +3175,22 @@ var CodeMirror = (function() { // See if "".split is the broken IE version, if so, provide an // alternative way to split lines. var splitLines = "\n\nb".split(/\n/).length != 3 ? 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; + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } } - result.push(string.slice(pos)); return result; - } : function(string){return string.split(/\r?\n/);}; + } : function(string){return string.split(/\r\n?|\n/);}; CodeMirror.splitLines = splitLines; var hasSelection = window.getSelection ? function(te) { @@ -2799,10 +3211,10 @@ var CodeMirror = (function() { var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",", - 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp", - 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right", - 63233: "Down", 63302: "Insert", 63272: "Delete"}; + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; CodeMirror.keyNames = keyNames; (function() { // Number keys diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/closetag.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/closetag.js new file mode 100644 index 0000000..a41268d --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/closetag.js @@ -0,0 +1,164 @@ +/** + * Tag-closer extension for CodeMirror. + * + * This extension adds a "closeTag" utility function that can be used with key bindings to + * insert a matching end tag after the ">" character of a start tag has been typed. It can + * also complete "" if a matching start tag is found. It will correctly ignore signal + * characters for empty tags, comments, CDATA, etc. + * + * The function depends on internal parser state to identify tags. It is compatible with the + * following CodeMirror modes and will ignore all others: + * - htmlmixed + * - xml + * + * See demos/closetag.html for a usage example. + * + * @author Nathan Williams+ * Contributed under the same license terms as CodeMirror. + */ +(function() { + /** Option that allows tag closing behavior to be toggled. Default is true. */ + CodeMirror.defaults['closeTagEnabled'] = true; + + /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */ + CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul']; + + /** Array of tag names where an end tag is forbidden. */ + CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; + + /** + * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass. + * - cm: The editor instance. + * - ch: The character being processed. + * - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option. + * Pass false to disable indentation. Pass an array to override the default list of tag names. + * - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode. + */ + CodeMirror.defineExtension("closeTag", function(cm, ch, indent, vd) { + if (!cm.getOption('closeTagEnabled')) { + throw CodeMirror.Pass; + } + + var mode = cm.getOption('mode'); + + if (mode == 'text/html' || mode == 'xml') { + + /* + * Relevant structure of token: + * + * htmlmixed + * className + * state + * htmlState + * type + * tagName + * context + * tagName + * mode + * + * xml + * className + * state + * tagName + * type + */ + + var pos = cm.getCursor(); + var tok = cm.getTokenAt(pos); + var state = tok.state; + + if (state.mode && state.mode != 'html') { + throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode. + } + + if (ch == '>') { + var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml + + if (tok.className == 'tag' && type == 'closeTag') { + throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag. + } + + cm.replaceSelection('>'); // Mode state won't update until we finish the tag. + pos = {line: pos.line, ch: pos.ch + 1}; + cm.setCursor(pos); + + tok = cm.getTokenAt(cm.getCursor()); + state = tok.state; + type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml + + if (tok.className == 'tag' && type != 'selfcloseTag') { + var tagName = state.htmlState ? state.htmlState.tagName : state.tagName; // htmlmixed : xml + if (tagName.length > 0 && shouldClose(cm, vd, tagName)) { + insertEndTag(cm, indent, pos, tagName); + } + return; + } + + // Undo the '>' insert and allow cm to handle the key instead. + cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos); + cm.replaceSelection(""); + + } else if (ch == '/') { + if (tok.className == 'tag' && tok.string == '<') { + var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : (state.context ? state.context.tagName : ''); // htmlmixed : xml + if (tagName.length > 0) { + completeEndTag(cm, pos, tagName); + return; + } + } + } + + } + + throw CodeMirror.Pass; // Bubble if not handled + }); + + function insertEndTag(cm, indent, pos, tagName) { + if (shouldIndent(cm, indent, tagName)) { + cm.replaceSelection('\n\n' + tagName + '>', 'end'); + cm.indentLine(pos.line + 1); + cm.indentLine(pos.line + 2); + cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length}); + } else { + cm.replaceSelection('' + tagName + '>'); + cm.setCursor(pos); + } + } + + function shouldIndent(cm, indent, tagName) { + if (typeof indent == 'undefined' || indent == null || indent == true) { + indent = cm.getOption('closeTagIndent'); + } + if (!indent) { + indent = []; + } + return indexOf(indent, tagName.toLowerCase()) != -1; + } + + function shouldClose(cm, vd, tagName) { + if (cm.getOption('mode') == 'xml') { + return true; // always close xml tags + } + if (typeof vd == 'undefined' || vd == null) { + vd = cm.getOption('closeTagVoid'); + } + if (!vd) { + vd = []; + } + return indexOf(vd, tagName.toLowerCase()) == -1; + } + + // C&P from codemirror.js...would be nice if this were visible to utilities. + function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; + } + + function completeEndTag(cm, pos, tagName) { + cm.replaceSelection('/' + tagName + '>'); + cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 }); + } + +})(); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.css b/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.css old mode 100755 new mode 100644 index 4cb467e..8c4f847 --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.css +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.css @@ -21,3 +21,7 @@ color: inherit; font-family: monospace; } + +.CodeMirror-dialog button { + font-size: 70%; +} \ No newline at end of file diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.js old mode 100755 new mode 100644 index 8950bf0..36ea10f --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/dialog.js @@ -17,7 +17,7 @@ closed = true; dialog.parentNode.removeChild(dialog); } - var inp = dialog.getElementsByTagName("input")[0]; + var inp = dialog.getElementsByTagName("input")[0], button; if (inp) { CodeMirror.connect(inp, "keydown", function(e) { if (e.keyCode == 13 || e.keyCode == 27) { @@ -29,6 +29,10 @@ }); inp.focus(); CodeMirror.connect(inp, "blur", close); + } else if (button = dialog.getElementsByTagName("button")[0]) { + CodeMirror.connect(button, "click", close); + button.focus(); + CodeMirror.connect(button, "blur", close); } return close; }); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/foldcode.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/foldcode.js old mode 100755 new mode 100644 index efd7f7c..02cfb50 --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/foldcode.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/foldcode.js @@ -1,9 +1,9 @@ // the tagRangeFinder function is // Copyright (C) 2011 by Daniel Glazman // released under the MIT license (../../LICENSE) like the rest of CodeMirror -CodeMirror.tagRangeFinder = function(cm, line) { +CodeMirror.tagRangeFinder = function(cm, line, hideEnd) { var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; - var nameChar = nameStartChar + "\-\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; + var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*"); var lineText = cm.getLine(line); @@ -36,8 +36,10 @@ CodeMirror.tagRangeFinder = function(cm, line) { var slash = lt.lastIndexOf("/", gt); if (-1 != slash && slash < gt) { var str = lineText.substr(slash, gt - slash + 1); - if (!str.match( /\/\s*\>/ )) // yep, that's the end of empty tag - return l+1; + if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag + if (hideEnd === true) l++; + return l; + } } } l++; @@ -80,7 +82,7 @@ CodeMirror.tagRangeFinder = function(cm, line) { } if (found) { - var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\s)|(\\<" + tag + "$)"; + var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)"; var startTagRegExp = new RegExp(startTag, "g"); var endTag = "" + tag + ">"; var depth = 1; @@ -95,8 +97,10 @@ CodeMirror.tagRangeFinder = function(cm, line) { depth--; else depth++; - if (!depth) - return l+1; + if (!depth) { + if (hideEnd === true) l++; + return l; + } } } l++; @@ -105,11 +109,16 @@ CodeMirror.tagRangeFinder = function(cm, line) { } }; -CodeMirror.braceRangeFinder = function(cm, line) { - var lineText = cm.getLine(line); - var startChar = lineText.lastIndexOf("{"); - if (startChar < 0 || lineText.lastIndexOf("}") > startChar) return; - var tokenType = cm.getTokenAt({line: line, ch: startChar}).className; +CodeMirror.braceRangeFinder = function(cm, line, hideEnd) { + var lineText = cm.getLine(line), at = lineText.length, startChar, tokenType; + for (;;) { + var found = lineText.lastIndexOf("{", at); + if (found < 0) break; + tokenType = cm.getTokenAt({line: line, ch: found}).className; + if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; } + at = found - 1; + } + if (startChar == null || lineText.lastIndexOf("}") > startChar) return; var count = 1, lastLine = cm.lineCount(), end; outer: for (var i = line + 1; i < lastLine; ++i) { var text = cm.getLine(i), pos = 0; @@ -127,11 +136,25 @@ CodeMirror.braceRangeFinder = function(cm, line) { } } if (end == null || end == line + 1) return; + if (hideEnd === true) end++; return end; }; +CodeMirror.indentRangeFinder = function(cm, line) { + var tabSize = cm.getOption("tabSize"); + var myIndent = cm.getLineHandle(line).indentation(tabSize), last; + for (var i = line + 1, end = cm.lineCount(); i < end; ++i) { + var handle = cm.getLineHandle(i); + if (!/^\s*$/.test(handle.text)) { + if (handle.indentation(tabSize) <= myIndent) break; + last = i; + } + } + if (!last) return null; + return last + 1; +}; -CodeMirror.newFoldFunction = function(rangeFinder, markText) { +CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) { var folded = []; if (markText == null) markText = ' ▼%N%'; @@ -156,7 +179,7 @@ CodeMirror.newFoldFunction = function(rangeFinder, markText) { folded.splice(known.pos, 1); expand(cm, known.region); } else { - var end = rangeFinder(cm, line); + var end = rangeFinder(cm, line, hideEnd); if (end == null) return; var hidden = []; for (var i = line + 1; i < end; ++i) { diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/formatting.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/formatting.js old mode 100755 new mode 100644 index f2a74be..ccac9b1 --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/formatting.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/formatting.js @@ -1,10 +1,13 @@ -// ============== Formatting extensions ============================ +// ============== Formatting extensions ============================ // A common storage for all mode-specific formatting features if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {}; // Returns the extension of the editor's current mode CodeMirror.defineExtension("getModeExt", function () { - return CodeMirror.modeExtensions[this.getOption("mode")]; + var mname = CodeMirror.resolveMode(this.getOption("mode")).name; + var ext = CodeMirror.modeExtensions[mname]; + if (!ext) throw new Error("No extensions found for mode " + mname); + return ext; }); // If the current mode is 'htmlmixed', returns the extension of a mode located at @@ -81,7 +84,8 @@ CodeMirror.modeExtensions["css"] = { commentStart: "/*", commentEnd: "*/", wordWrapChars: [";", "\\{", "\\}"], - autoFormatLineBreaks: function (text) { + autoFormatLineBreaks: function (text, startPos, endPos) { + text = text.substring(startPos, endPos); return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); } }; @@ -94,6 +98,8 @@ CodeMirror.modeExtensions["javascript"] = { getNonBreakableBlocks: function (text) { var nonBreakableRegexes = [ new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"), + new RegExp("\\\\\"([\\s\\S]*?)(\\\\\"|$)"), + new RegExp("\\\\\'([\\s\\S]*?)(\\\\\'|$)"), new RegExp("'([\\s\\S]*?)('|$)"), new RegExp("\"([\\s\\S]*?)(\"|$)"), new RegExp("//.*([\r\n]|$)") @@ -122,7 +128,8 @@ CodeMirror.modeExtensions["javascript"] = { return nonBreakableBlocks; }, - autoFormatLineBreaks: function (text) { + autoFormatLineBreaks: function (text, startPos, endPos) { + text = text.substring(startPos, endPos); var curPos = 0; var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g"); var nonBreakableBlocks = this.getNonBreakableBlocks(text); @@ -155,7 +162,8 @@ CodeMirror.modeExtensions["xml"] = { commentEnd: "-->", wordWrapChars: [">"], - autoFormatLineBreaks: function (text) { + autoFormatLineBreaks: function (text, startPos, endPos) { + text = text.substring(startPos, endPos); var lines = text.split("\n"); var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); var reOpenBrackets = new RegExp("<", "g"); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/javascript-hint.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/javascript-hint.js old mode 100755 new mode 100644 index 4e88a7e..2117e5a --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/javascript-hint.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/javascript-hint.js @@ -15,37 +15,81 @@ } return arr.indexOf(item) != -1; } - - CodeMirror.javascriptHint = function(editor) { + + function scriptHint(editor, keywords, getToken) { // Find the token at the cursor - var cur = editor.getCursor(), token = editor.getTokenAt(cur), tprop = token; + var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; // If it's not a 'word-style' token, ignore the token. - if (!/^[\w$_]*$/.test(token.string)) { + if (!/^[\w$_]*$/.test(token.string)) { token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, className: token.string == "." ? "property" : null}; } // If it is a property, find out what it is a property of. while (tprop.className == "property") { - tprop = editor.getTokenAt({line: cur.line, ch: tprop.start}); + tprop = getToken(editor, {line: cur.line, ch: tprop.start}); if (tprop.string != ".") return; - tprop = editor.getTokenAt({line: cur.line, ch: tprop.start}); + tprop = getToken(editor, {line: cur.line, ch: tprop.start}); + if (tprop.string == ')') { + var level = 1; + do { + tprop = getToken(editor, {line: cur.line, ch: tprop.start}); + switch (tprop.string) { + case ')': level++; break; + case '(': level--; break; + default: break; + } + } while (level > 0) + tprop = getToken(editor, {line: cur.line, ch: tprop.start}); + if (tprop.className == 'variable') + tprop.className = 'function'; + else return; // no clue + } if (!context) var context = []; context.push(tprop); } - return {list: getCompletions(token, context), + return {list: getCompletions(token, context, keywords), from: {line: cur.line, ch: token.start}, to: {line: cur.line, ch: token.end}}; } + CodeMirror.javascriptHint = function(editor) { + return scriptHint(editor, javascriptKeywords, + function (e, cur) {return e.getTokenAt(cur);}); + } + + function getCoffeeScriptToken(editor, cur) { + // This getToken, it is for coffeescript, imitates the behavior of + // getTokenAt method in javascript.js, that is, returning "property" + // type and treat "." as indepenent token. + var token = editor.getTokenAt(cur); + if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { + token.end = token.start; + token.string = '.'; + token.className = "property"; + } + else if (/^\.[\w$_]*$/.test(token.string)) { + token.className = "property"; + token.start++; + token.string = token.string.replace(/\./, ''); + } + return token; + } + + CodeMirror.coffeescriptHint = function(editor) { + return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken); + } + var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + "toUpperCase toLowerCase split concat match replace search").split(" "); var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); var funcProps = "prototype apply call bind".split(" "); - var keywords = ("break case catch continue debugger default delete do else false finally for function " + + var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " + "if in instanceof new null return switch throw true try typeof var void while with").split(" "); + var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + + "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); - function getCompletions(token, context) { + function getCompletions(token, context, keywords) { var found = [], start = token.string; function maybeAdd(str) { if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); @@ -67,6 +111,13 @@ base = ""; else if (obj.className == "atom") base = 1; + else if (obj.className == "function") { + if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && + (typeof window.jQuery == 'function')) + base = window.jQuery(); + else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function')) + base = window._(); + } while (base != null && context.length) base = base[context.pop().string]; if (base != null) gatherCompletions(base); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/loadmode.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/loadmode.js new file mode 100644 index 0000000..48d5a7a --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/loadmode.js @@ -0,0 +1,51 @@ +(function() { + if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js"; + + var loading = {}; + function splitCallback(cont, n) { + var countDown = n; + return function() { if (--countDown == 0) cont(); } + } + function ensureDeps(mode, cont) { + var deps = CodeMirror.modes[mode].dependencies; + if (!deps) return cont(); + var missing = []; + for (var i = 0; i < deps.length; ++i) { + if (!CodeMirror.modes.hasOwnProperty(deps[i])) + missing.push(deps[i]); + } + if (!missing.length) return cont(); + var split = splitCallback(cont, missing.length); + for (var i = 0; i < missing.length; ++i) + CodeMirror.requireMode(missing[i], split); + } + + CodeMirror.requireMode = function(mode, cont) { + if (typeof mode != "string") mode = mode.name; + if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont); + if (loading.hasOwnProperty(mode)) return loading[mode].push(cont); + + var script = document.createElement("script"); + script.src = CodeMirror.modeURL.replace(/%N/g, mode); + var others = document.getElementsByTagName("script")[0]; + others.parentNode.insertBefore(script, others); + var list = loading[mode] = [cont]; + var count = 0, poll = setInterval(function() { + if (++count > 100) return clearInterval(poll); + if (CodeMirror.modes.hasOwnProperty(mode)) { + clearInterval(poll); + loading[mode] = null; + ensureDeps(mode, function() { + for (var i = 0; i < list.length; ++i) list[i](); + }); + } + }, 200); + }; + + CodeMirror.autoLoadMode = function(instance, mode) { + if (!CodeMirror.modes.hasOwnProperty(mode)) + CodeMirror.requireMode(mode, function() { + instance.setOption("mode", instance.getOption("mode")); + }); + }; +}()); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/match-highlighter.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/match-highlighter.js new file mode 100644 index 0000000..59098ff --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/match-highlighter.js @@ -0,0 +1,44 @@ +// Define match-highlighter commands. Depends on searchcursor.js +// Use by attaching the following function call to the onCursorActivity event: + //myCodeMirror.matchHighlight(minChars); +// And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html) + +(function() { + var DEFAULT_MIN_CHARS = 2; + + function MatchHighlightState() { + this.marked = []; + } + function getMatchHighlightState(cm) { + return cm._matchHighlightState || (cm._matchHighlightState = new MatchHighlightState()); + } + + function clearMarks(cm) { + var state = getMatchHighlightState(cm); + for (var i = 0; i < state.marked.length; ++i) + state.marked[i].clear(); + state.marked = []; + } + + function markDocument(cm, className, minChars) { + clearMarks(cm); + minChars = (typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS); + if (cm.somethingSelected() && cm.getSelection().replace(/^\s+|\s+$/g, "").length >= minChars) { + var state = getMatchHighlightState(cm); + var query = cm.getSelection(); + cm.operation(function() { + if (cm.lineCount() < 2000) { // This is too expensive on big documents. + for (var cursor = cm.getSearchCursor(query); cursor.findNext();) { + //Only apply matchhighlight to the matches other than the one actually selected + if (!(cursor.from().line === cm.getCursor(true).line && cursor.from().ch === cm.getCursor(true).ch)) + state.marked.push(cm.markText(cursor.from(), cursor.to(), className)); + } + } + }); + } + } + + CodeMirror.defineExtension("matchHighlight", function(className, minChars) { + markDocument(this, className, minChars); + }); +})(); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/multiplex.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/multiplex.js new file mode 100644 index 0000000..c8bb1e9 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/multiplex.js @@ -0,0 +1,81 @@ +CodeMirror.multiplexingMode = function(outer /*, others */) { + // Others should be {open, close, mode [, delimStyle]} objects + var others = Array.prototype.slice.call(arguments, 1); + var n_others = others.length; + + function indexOf(string, pattern, from) { + if (typeof pattern == "string") return string.indexOf(pattern, from); + var m = pattern.exec(from ? string.slice(from) : string); + return m ? m.index + from : -1; + } + + return { + startState: function() { + return { + outer: CodeMirror.startState(outer), + innerActive: null, + inner: null + }; + }, + + copyState: function(state) { + return { + outer: CodeMirror.copyState(outer, state.outer), + innerActive: state.innerActive, + inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner) + }; + }, + + token: function(stream, state) { + if (!state.innerActive) { + var cutOff = Infinity, oldContent = stream.string; + for (var i = 0; i < n_others; ++i) { + var other = others[i]; + var found = indexOf(oldContent, other.open, stream.pos); + if (found == stream.pos) { + stream.match(other.open); + state.innerActive = other; + state.inner = CodeMirror.startState(other.mode, outer.indent(state.outer, "")); + return other.delimStyle; + } else if (found != -1 && found < cutOff) { + cutOff = found; + } + } + if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff); + var outerToken = outer.token(stream, state.outer); + if (cutOff != Infinity) stream.string = oldContent; + return outerToken; + } else { + var curInner = state.innerActive, oldContent = stream.string; + var found = indexOf(oldContent, curInner.close, stream.pos); + if (found == stream.pos) { + stream.match(curInner.close); + state.innerActive = state.inner = null; + return curInner.delimStyle; + } + if (found > -1) stream.string = oldContent.slice(0, found); + var innerToken = curInner.mode.token(stream, state.inner); + if (found > -1) stream.string = oldContent; + var cur = stream.current(), found = cur.indexOf(curInner.close); + if (found > -1) stream.backUp(cur.length - found); + return innerToken; + } + }, + + indent: function(state, textAfter) { + var mode = state.innerActive ? state.innerActive.mode : outer; + if (!mode.indent) return CodeMirror.Pass; + return mode.indent(state.innerActive ? state.inner : state.outer, textAfter); + }, + + compareStates: function(a, b) { + if (a.innerActive != b.innerActive) return false; + var mode = a.innerActive || outer; + if (!mode.compareStates) return CodeMirror.Pass; + return mode.compareStates(a.innerActive ? a.inner : a.outer, + b.innerActive ? b.inner : b.outer); + }, + + electricChars: outer.electricChars + }; +}; diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/overlay.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/overlay.js old mode 100755 new mode 100644 index c4cdf9f..1d5df6c --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/overlay.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/overlay.js @@ -6,7 +6,8 @@ // overlay wins, unless the combine argument was true, in which case // the styles are combined. -CodeMirror.overlayParser = function(base, overlay, combine) { +// overlayParser is the old, deprecated name +CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) { return { startState: function() { return { @@ -43,7 +44,7 @@ CodeMirror.overlayParser = function(base, overlay, combine) { else return state.overlayCur; }, - indent: function(state, textAfter) { + indent: base.indent && function(state, textAfter) { return base.indent(state.base, textAfter); }, electricChars: base.electricChars diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/pig-hint.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/pig-hint.js new file mode 100644 index 0000000..d696c3a --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/pig-hint.js @@ -0,0 +1,123 @@ +(function () { + function forEach(arr, f) { + for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); + } + + function arrayContains(arr, item) { + if (!Array.prototype.indexOf) { + var i = arr.length; + while (i--) { + if (arr[i] === item) { + return true; + } + } + return false; + } + return arr.indexOf(item) != -1; + } + + function scriptHint(editor, keywords, getToken) { + // Find the token at the cursor + var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; + // If it's not a 'word-style' token, ignore the token. + + if (!/^[\w$_]*$/.test(token.string)) { + token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, + className: token.string == ":" ? "pig-type" : null}; + } + + if (!context) var context = []; + context.push(tprop); + + var completionList = getCompletions(token, context); + completionList = completionList.sort(); + //prevent autocomplete for last word, instead show dropdown with one word + if(completionList.length == 1) { + completionList.push(" "); + } + + return {list: completionList, + from: {line: cur.line, ch: token.start}, + to: {line: cur.line, ch: token.end}}; + } + + CodeMirror.pigHint = function(editor) { + return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);}); + } + + function toTitleCase(str) { + return str.replace(/(?:^|\s)\w/g, function(match) { + return match.toUpperCase(); + }); + } + + var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " + + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " + + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " + + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " + + "NEQ MATCHES TRUE FALSE"; + var pigKeywordsU = pigKeywords.split(" "); + var pigKeywordsL = pigKeywords.toLowerCase().split(" "); + + var pigTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP"; + var pigTypesU = pigTypes.split(" "); + var pigTypesL = pigTypes.toLowerCase().split(" "); + + var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " + + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS " + + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG " + + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN " + + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER " + + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS " + + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA " + + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE " + + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG " + + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER"; + var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" "); + var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" "); + var pigBuiltinsC = ("BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs " + + "DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax " + + "FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum " + + "InvokeForDouble InvokeForFloat InvokeForInt InvokeForLong InvokeForString Invoker " + + "IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize " + + "MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax " + + "StringMin StringSize TextLoader TupleSize Utf8StorageConverter").split(" ").join("() ").split(" "); + + function getCompletions(token, context) { + var found = [], start = token.string; + function maybeAdd(str) { + if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + } + + function gatherCompletions(obj) { + if(obj == ":") { + forEach(pigTypesL, maybeAdd); + } + else { + forEach(pigBuiltinsU, maybeAdd); + forEach(pigBuiltinsL, maybeAdd); + forEach(pigBuiltinsC, maybeAdd); + forEach(pigTypesU, maybeAdd); + forEach(pigTypesL, maybeAdd); + forEach(pigKeywordsU, maybeAdd); + forEach(pigKeywordsL, maybeAdd); + } + } + + if (context) { + // If this is a property, see if it belongs to some object we can + // find in the current environment. + var obj = context.pop(), base; + + if (obj.className == "pig-word") + base = obj.string; + else if(obj.className == "pig-type") + base = ":" + obj.string; + + while (base != null && context.length) + base = base[context.pop().string]; + if (base != null) gatherCompletions(base); + } + return found; + } +})(); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/runmode.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/runmode.js old mode 100755 new mode 100644 index de4a760..fc58d85 --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/runmode.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/runmode.js @@ -1,15 +1,37 @@ -CodeMirror.runMode = function(string, modespec, callback) { - var mode = CodeMirror.getMode({indentUnit: 2}, modespec); +CodeMirror.runMode = function(string, modespec, callback, options) { + var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); var isNode = callback.nodeType == 1; + var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; if (isNode) { - var node = callback, accum = []; - callback = function(string, style) { - if (string == "\n") + var node = callback, accum = [], col = 0; + callback = function(text, style) { + if (text == "\n") { accum.push("
"); - else if (style) - accum.push("" + CodeMirror.htmlEscape(string) + ""); + col = 0; + return; + } + var escaped = ""; + // HTML-escape and replace tabs + for (var pos = 0;;) { + var idx = text.indexOf("\t", pos); + if (idx == -1) { + escaped += CodeMirror.htmlEscape(text.slice(pos)); + col += text.length - pos; + break; + } else { + col += idx - pos; + escaped += CodeMirror.htmlEscape(text.slice(pos, idx)); + var size = tabSize - col % tabSize; + col += size; + for (var i = 0; i < size; ++i) escaped += " "; + pos = idx + 1; + } + } + + if (style) + accum.push("" + escaped + ""); else - accum.push(CodeMirror.htmlEscape(string)); + accum.push(escaped); } } var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/search.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/search.js old mode 100755 new mode 100644 index 63ebca9..c5a2bcc --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/search.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/search.js @@ -14,6 +14,10 @@ function getSearchState(cm) { return cm._searchState || (cm._searchState = new SearchState()); } + function getSearchCursor(cm, query, pos) { + // Heuristic: if the query string is all lowercase, do a case insensitive search. + return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase()); + } function dialog(cm, text, shortText, f) { if (cm.openDialog) cm.openDialog(text, f); else f(prompt(shortText, "")); @@ -23,11 +27,11 @@ else if (confirm(shortText)) fs[0](); } function parseQuery(query) { - var isRE = query.match(/^\/(.*)\/$/); - return isRE ? new RegExp(isRE[1]) : query; + var isRE = query.match(/^\/(.*)\/([a-z]*)$/); + return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query; } var queryDialog = - 'Search: (Use /re/ syntax for regexp search)'; + 'Search: (Use /re/ syntax for regexp search)'; function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); @@ -36,7 +40,7 @@ if (!query || state.query) return; state.query = parseQuery(query); if (cm.lineCount() < 2000) { // This is too expensive on big documents. - for (var cursor = cm.getSearchCursor(query); cursor.findNext();) + for (var cursor = getSearchCursor(cm, query); cursor.findNext();) state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching")); } state.posFrom = state.posTo = cm.getCursor(); @@ -46,9 +50,9 @@ } function findNext(cm, rev) {cm.operation(function() { var state = getSearchState(cm); - var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo); + var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); if (!cursor.find(rev)) { - cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0}); + cursor = getSearchCursor(cm, state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0}); if (!cursor.find(rev)) return; } cm.setSelection(cursor.from(), cursor.to()); @@ -63,8 +67,8 @@ })} var replaceQueryDialog = - 'Replace: (Use /re/ syntax for regexp search)'; - var replacementQueryDialog = 'With: '; + 'Replace: (Use /re/ syntax for regexp search)'; + var replacementQueryDialog = 'With: '; var doReplaceConfirm = "Replace? "; function replace(cm, all) { dialog(cm, replaceQueryDialog, "Replace:", function(query) { @@ -72,23 +76,23 @@ query = parseQuery(query); dialog(cm, replacementQueryDialog, "Replace with:", function(text) { if (all) { - cm.operation(function() { - for (var cursor = cm.getSearchCursor(query); cursor.findNext();) { + cm.compoundChange(function() { cm.operation(function() { + for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { if (typeof query != "string") { var match = cm.getRange(cursor.from(), cursor.to()).match(query); cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];})); } else cursor.replace(text); } - }); + })}); } else { clearSearch(cm); - var cursor = cm.getSearchCursor(query, cm.getCursor()); + var cursor = getSearchCursor(cm, query, cm.getCursor()); function advance() { var start = cursor.from(), match; if (!(match = cursor.findNext())) { - cursor = cm.getSearchCursor(query); + cursor = getSearchCursor(cm, query); if (!(match = cursor.findNext()) || - (cursor.from().line == start.line && cursor.from().ch == start.ch)) return; + (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; } cm.setSelection(cursor.from(), cursor.to()); confirmDialog(cm, doReplaceConfirm, "Replace?", diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/searchcursor.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/searchcursor.js old mode 100755 new mode 100644 index 3b77829..ec3f73c --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/searchcursor.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/searchcursor.js @@ -1,7 +1,7 @@ (function(){ function SearchCursor(cm, query, pos, caseFold) { this.atOccurrence = false; this.cm = cm; - if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase(); + if (caseFold == null && typeof query == "string") caseFold = false; pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0}; this.pos = {from: pos, to: pos}; diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/simple-hint.css b/IPython/frontend/html/notebook/static/codemirror/lib/util/simple-hint.css old mode 100755 new mode 100644 diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/simple-hint.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/simple-hint.js old mode 100755 new mode 100644 index b38f389..7eef32c --- a/IPython/frontend/html/notebook/static/codemirror/lib/util/simple-hint.js +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/simple-hint.js @@ -2,6 +2,10 @@ CodeMirror.simpleHint = function(editor, getHints) { // We want a single cursor position. if (editor.somethingSelected()) return; + //don't show completion if the token is empty + var tempToken = editor.getTokenAt(editor.getCursor()); + if(!(/[\S]/gi.test(tempToken.string))) return; + var result = getHints(editor); if (!result || !result.list.length) return; var completions = result.list; @@ -29,6 +33,10 @@ complete.style.left = pos.x + "px"; complete.style.top = pos.yBot + "px"; document.body.appendChild(complete); + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. + var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); + if(winW - pos.x < sel.clientWidth) + complete.style.left = (pos.x - sel.clientWidth) + "px"; // Hack to hide the scrollbar. if (completions.length <= 10) complete.style.width = (sel.clientWidth - 1) + "px"; @@ -53,6 +61,8 @@ else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();} else if (code != 38 && code != 40) { close(); editor.focus(); + // Pass the event to the CodeMirror instance so that it can handle things like backspace properly. + editor.triggerOnKeyDown(event); setTimeout(function(){CodeMirror.simpleHint(editor, getHints);}, 50); } }); diff --git a/IPython/frontend/html/notebook/static/codemirror/lib/util/xml-hint.js b/IPython/frontend/html/notebook/static/codemirror/lib/util/xml-hint.js new file mode 100644 index 0000000..677661a --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/lib/util/xml-hint.js @@ -0,0 +1,137 @@ + +(function() { + + CodeMirror.xmlHints = []; + + CodeMirror.xmlHint = function(cm, simbol) { + + if(simbol.length > 0) { + var cursor = cm.getCursor(); + cm.replaceSelection(simbol); + cursor = {line: cursor.line, ch: cursor.ch + 1}; + cm.setCursor(cursor); + } + + // dirty hack for simple-hint to receive getHint event on space + var getTokenAt = editor.getTokenAt; + + editor.getTokenAt = function() { return 'disabled'; } + CodeMirror.simpleHint(cm, getHint); + + editor.getTokenAt = getTokenAt; + }; + + var getHint = function(cm) { + + var cursor = cm.getCursor(); + + if (cursor.ch > 0) { + + var text = cm.getRange({line: 0, ch: 0}, cursor); + var typed = ''; + var simbol = ''; + for(var i = text.length - 1; i >= 0; i--) { + if(text[i] == ' ' || text[i] == '<') { + simbol = text[i]; + break; + } + else { + typed = text[i] + typed; + } + } + + text = text.substr(0, text.length - typed.length); + + var path = getActiveElement(cm, text) + simbol; + var hints = CodeMirror.xmlHints[path]; + + if(typeof hints === 'undefined') + hints = ['']; + else { + hints = hints.slice(0); + for (var i = hints.length - 1; i >= 0; i--) { + if(hints[i].indexOf(typed) != 0) + hints.splice(i, 1); + } + } + + return { + list: hints, + from: { line: cursor.line, ch: cursor.ch - typed.length }, + to: cursor, + }; + }; + } + + var getActiveElement = function(codeMirror, text) { + + var element = ''; + + if(text.length >= 0) { + + var regex = new RegExp('<([^!?][^\\s/>]*).*?>', 'g'); + + var matches = []; + var match; + while ((match = regex.exec(text)) != null) { + matches.push({ + tag: match[1], + selfclose: (match[0].substr(-1) === '/>') + }); + } + + for (var i = matches.length - 1, skip = 0; i >= 0; i--) { + + var item = matches[i]; + + if (item.tag[0] == '/') + { + skip++; + } + else if (item.selfclose == false) + { + if (skip > 0) + { + skip--; + } + else + { + element = '<' + item.tag + '>' + element; + } + } + } + + element += getOpenTag(text); + } + + return element; + }; + + var getOpenTag = function(text) { + + var open = text.lastIndexOf('<'); + var close = text.lastIndexOf('>'); + + if (close < open) + { + text = text.substr(open); + + if(text != '<') { + + var space = text.indexOf(' '); + if(space < 0) + space = text.indexOf('\t'); + if(space < 0) + space = text.indexOf('\n'); + + if (space < 0) + space = text.length; + + return text.substr(0, space); + } + } + + return ''; + }; + +})(); diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/clike/clike.js b/IPython/frontend/html/notebook/static/codemirror/mode/clike/clike.js new file mode 100644 index 0000000..e0c7d18 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/clike/clike.js @@ -0,0 +1,280 @@ +CodeMirror.defineMode("clike", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords || {}, + builtin = parserConfig.builtin || {}, + blockKeywords = parserConfig.blockKeywords || {}, + atoms = parserConfig.atoms || {}, + hooks = parserConfig.hooks || {}, + multiLineStrings = parserConfig.multiLineStrings; + var isOperatorChar = /[+\-*&%=<>!?|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } + if (builtin.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "builtin"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}" + }; +}); + +(function() { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var cKeywords = "auto if break int case long char register continue return default short do sizeof " + + "double static else struct entry switch extern typedef float union for unsigned " + + "goto while enum void const signed volatile"; + + function cppHook(stream, state) { + if (!state.startOfLine) return false; + stream.skipToEnd(); + return "meta"; + } + + // C#-style strings where "" escapes a quote. + function tokenAtString(stream, state) { + var next; + while ((next = stream.next()) != null) { + if (next == '"' && !stream.eat('"')) { + state.tokenize = null; + break; + } + } + return "string"; + } + + CodeMirror.defineMIME("text/x-csrc", { + name: "clike", + keywords: words(cKeywords), + blockKeywords: words("case do else for if switch while struct"), + atoms: words("null"), + hooks: {"#": cppHook} + }); + CodeMirror.defineMIME("text/x-c++src", { + name: "clike", + keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + + "static_cast typeid catch operator template typename class friend private " + + "this using const_cast inline public throw virtual delete mutable protected " + + "wchar_t"), + blockKeywords: words("catch class do else finally for if struct switch try while"), + atoms: words("true false null"), + hooks: {"#": cppHook} + }); + CodeMirror.defineMIME("text/x-java", { + name: "clike", + keywords: words("abstract assert boolean break byte case catch char class const continue default " + + "do double else enum extends final finally float for goto if implements import " + + "instanceof int interface long native new package private protected public " + + "return short static strictfp super switch synchronized this throw throws transient " + + "try void volatile while"), + blockKeywords: words("catch class do else finally for if switch try while"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + CodeMirror.defineMIME("text/x-csharp", { + name: "clike", + keywords: words("abstract as base break case catch checked class const continue" + + " default delegate do else enum event explicit extern finally fixed for" + + " foreach goto if implicit in interface internal is lock namespace new" + + " operator out override params private protected public readonly ref return sealed" + + " sizeof stackalloc static struct switch this throw try typeof unchecked" + + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + + " global group into join let orderby partial remove select set value var yield"), + blockKeywords: words("catch class do else finally for foreach if struct switch try while"), + builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + + " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + + " UInt64 bool byte char decimal double short int long object" + + " sbyte float string ushort uint ulong"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + if (stream.eat('"')) { + state.tokenize = tokenAtString; + return tokenAtString(stream, state); + } + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + CodeMirror.defineMIME("text/x-scala", { + name: "clike", + keywords: words( + + /* scala */ + "abstract case catch class def do else extends false final finally for forSome if " + + "implicit import lazy match new null object override package private protected return " + + "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " + + "<% >: # @ " + + + /* package scala */ + "assert assume require print println printf readLine readBoolean readByte readShort " + + "readChar readInt readLong readFloat readDouble " + + + "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + + "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + + "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + + "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + + "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " + + + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" + + + ), + blockKeywords: words("catch class do else finally for forSome if match switch try while"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); +}()); diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/clike/index.html b/IPython/frontend/html/notebook/static/codemirror/mode/clike/index.html new file mode 100644 index 0000000..64d02f1 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/clike/index.html @@ -0,0 +1,101 @@ + + + +CodeMirror: C-like mode + + + + + + + +CodeMirror: C-like mode
+ + + + + +Simple mode that tries to handle C-like languages as well as it + can. Takes two configuration parameters:
+ +keywords
, an + object whose property names are the keywords in the language, + anduseCPP
, which determines whether C preprocessor + directives are recognized.MIME types defined:
+ + diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/clike/scala.html b/IPython/frontend/html/notebook/static/codemirror/mode/clike/scala.html new file mode 100644 index 0000000..5fdd84e --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/clike/scala.html @@ -0,0 +1,765 @@ + + + +text/x-csrc
+ (C code),text/x-c++src
(C++ + code),text/x-java
(Java + code),text/x-csharp
(C#).CodeMirror: C-like mode + + + + + + + + + + + + + diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/clojure/clojure.js b/IPython/frontend/html/notebook/static/codemirror/mode/clojure/clojure.js new file mode 100644 index 0000000..c683641 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/clojure/clojure.js @@ -0,0 +1,207 @@ +/** + * Author: Hans Engel + * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun) + */ +CodeMirror.defineMode("clojure", function (config, mode) { + var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", TAG = "tag", + ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword"; + var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1; + + function makeKeywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var atoms = makeKeywords("true false nil"); + + var keywords = makeKeywords( + "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle"); + + var builtins = makeKeywords( + "* *1 *2 *3 *agent* *allow-unresolved-vars* *assert *clojure-version* *command-line-args* *compile-files* *compile-path* *e *err* *file* *flush-on-newline* *in* *macro-meta* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *use-context-classloader* *warn-on-reflection* + - / < <= = == > >= accessor aclone agent agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec decimal? declare definline defmacro defmethod defmulti defn defn- defonce defstruct delay delay? deliver deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall doc dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq eval even? every? extend extend-protocol extend-type extends? extenders false? ffirst file-seq filter find find-doc find-ns find-var first float float-array float? floats flush fn fn? fnext for force format future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator hash hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map? mapcat max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod name namespace neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext num number? odd? or parents partial partition pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-doc print-dup print-method print-namespace-doc print-simple print-special-doc print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string reify reduce ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure release-pending-sends rem remove remove-method remove-ns repeat repeatedly replace replicate require reset! reset-meta! resolve rest resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-validator! set? short short-array shorts shutdown-agents slurp some sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-form-anchor special-symbol? split-at split-with str stream? string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync syntax-symbol-anchor take take-last take-nth take-while test the-ns time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-dec unchecked-divide unchecked-inc unchecked-multiply unchecked-negate unchecked-remainder unchecked-subtract underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision xml-seq"); + + var indentKeys = makeKeywords( + // Built-ins + "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " + + + // Binding forms + "let letfn binding loop for doseq dotimes when-let if-let " + + + // Data structures + "defstruct struct-map assoc " + + + // clojure.test + "testing deftest " + + + // contrib + "handler-case handle dotrace deftrace"); + + var tests = { + digit: /\d/, + digit_or_colon: /[\d:]/, + hex: /[0-9a-fA-F]/, + sign: /[+-]/, + exponent: /[eE]/, + keyword_char: /[^\s\(\[\;\)\]]/, + basic: /[\w\$_\-]/, + lang_keyword: /[\w*+!\-_?:\/]/ + }; + + function stateStack(indent, type, prev) { // represents a state stack object + this.indent = indent; + this.type = type; + this.prev = prev; + } + + function pushStack(state, indent, type) { + state.indentStack = new stateStack(indent, type, state.indentStack); + } + + function popStack(state) { + state.indentStack = state.indentStack.prev; + } + + function isNumber(ch, stream){ + // hex + if ( ch === '0' && 'x' == stream.peek().toLowerCase() ) { + stream.eat('x'); + stream.eatWhile(tests.hex); + return true; + } + + // leading sign + if ( ch == '+' || ch == '-' ) { + stream.eat(tests.sign); + ch = stream.next(); + } + + if ( tests.digit.test(ch) ) { + stream.eat(ch); + stream.eatWhile(tests.digit); + + if ( '.' == stream.peek() ) { + stream.eat('.'); + stream.eatWhile(tests.digit); + } + + if ( 'e' == stream.peek().toLowerCase() ) { + stream.eat(tests.exponent); + stream.eat(tests.sign); + stream.eatWhile(tests.digit); + } + + return true; + } + + return false; + } + + return { + startState: function () { + return { + indentStack: null, + indentation: 0, + mode: false + }; + }, + + token: function (stream, state) { + if (state.indentStack == null && stream.sol()) { + // update indentation, but only if indentStack is empty + state.indentation = stream.indentation(); + } + + // skip spaces + if (stream.eatSpace()) { + return null; + } + var returnType = null; + + switch(state.mode){ + case "string": // multi-line string parsing mode + var next, escaped = false; + while ((next = stream.next()) != null) { + if (next == "\"" && !escaped) { + + state.mode = false; + break; + } + escaped = !escaped && next == "\\"; + } + returnType = STRING; // continue on in string mode + break; + default: // default parsing mode + var ch = stream.next(); + + if (ch == "\"") { + state.mode = "string"; + returnType = STRING; + } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) { + returnType = ATOM; + } else if (ch == ";") { // comment + stream.skipToEnd(); // rest of the line is a comment + returnType = COMMENT; + } else if (isNumber(ch,stream)){ + returnType = NUMBER; + } else if (ch == "(" || ch == "[") { + var keyWord = ''; var indentTemp = stream.column(); + /** + Either + (indent-word .. + (non-indent-word .. + (;something else, bracket, etc. + */ + + if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) { + keyWord += letter; + } + + if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word + pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); + } else { // non-indent word + // we continue eating the spaces + stream.eatSpace(); + if (stream.eol() || stream.peek() == ";") { + // nothing significant after + // we restart indentation 1 space after + pushStack(state, indentTemp + 1, ch); + } else { + pushStack(state, indentTemp + stream.current().length, ch); // else we match + } + } + stream.backUp(stream.current().length - 1); // undo all the eating + + returnType = BRACKET; + } else if (ch == ")" || ch == "]") { + returnType = BRACKET; + if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { + popStack(state); + } + } else if ( ch == ":" ) { + stream.eatWhile(tests.lang_keyword); + return ATOM; + } else { + stream.eatWhile(tests.basic); + + if (keywords && keywords.propertyIsEnumerable(stream.current())) { + returnType = KEYWORD; + } else if (builtins && builtins.propertyIsEnumerable(stream.current())) { + returnType = BUILTIN; + } else if (atoms && atoms.propertyIsEnumerable(stream.current())) { + returnType = ATOM; + } else returnType = null; + } + } + + return returnType; + }, + + indent: function (state, textAfter) { + if (state.indentStack == null) return state.indentation; + return state.indentStack.indent; + } + }; +}); + +CodeMirror.defineMIME("text/x-clojure", "clojure"); diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/clojure/index.html b/IPython/frontend/html/notebook/static/codemirror/mode/clojure/index.html new file mode 100644 index 0000000..9762d58 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/clojure/index.html @@ -0,0 +1,66 @@ + + + +CodeMirror: Clojure mode + + + + + + + +CodeMirror: Clojure mode
+ + + +MIME types defined:
+ + + diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/LICENSE b/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/LICENSE new file mode 100644 index 0000000..977e284 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2011 Jeff Pickhardt +Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/coffeescript.js b/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/coffeescript.js new file mode 100644 index 0000000..d8bfe36 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/coffeescript.js @@ -0,0 +1,346 @@ +/** + * Link to the project's GitHub page: + * https://github.com/pickhardt/coffeescript-codemirror-mode + */ +CodeMirror.defineMode('coffeescript', function(conf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]"); + var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]'); + var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))"); + var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); + var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))"); + var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*"); + var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*"); + + var wordOperators = wordRegexp(['and', 'or', 'not', + 'is', 'isnt', 'in', + 'instanceof', 'typeof']); + var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else', + 'switch', 'try', 'catch', 'finally', 'class']; + var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete', + 'do', 'in', 'of', 'new', 'return', 'then', + 'this', 'throw', 'when', 'until']; + + var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); + + indentKeywords = wordRegexp(indentKeywords); + + + var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])"); + var regexPrefixes = new RegExp("^(/{3}|/)"); + var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no']; + var constants = wordRegexp(commonConstants); + + // Tokenizers + function tokenBase(stream, state) { + // Handle scope changes + if (stream.sol()) { + var scopeOffset = state.scopes[0].offset; + if (stream.eatSpace()) { + var lineOffset = stream.indentation(); + if (lineOffset > scopeOffset) { + return 'indent'; + } else if (lineOffset < scopeOffset) { + return 'dedent'; + } + return null; + } else { + if (scopeOffset > 0) { + dedent(stream, state); + } + } + } + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle docco title comment (single line) + if (stream.match("####")) { + stream.skipToEnd(); + return 'comment'; + } + + // Handle multi line comments + if (stream.match("###")) { + state.tokenize = longComment; + return state.tokenize(stream, state); + } + + // Single line comment + if (ch === '#') { + stream.skipToEnd(); + return 'comment'; + } + + // Handle number literals + if (stream.match(/^-?[0-9\.]/, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { + floatLiteral = true; + } + if (stream.match(/^-?\d+\.\d*/)) { + floatLiteral = true; + } + if (stream.match(/^-?\.\d+/)) { + floatLiteral = true; + } + + if (floatLiteral) { + // prevent from getting extra . on 1.. + if (stream.peek() == "."){ + stream.backUp(1); + } + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^-?0x[0-9a-f]+/i)) { + intLiteral = true; + } + // Decimal + if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { + intLiteral = true; + } + // Zero by itself with no other piece of number. + if (stream.match(/^-?0(?![\dx])/i)) { + intLiteral = true; + } + if (intLiteral) { + return 'number'; + } + } + + // Handle strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenFactory(stream.current(), 'string'); + return state.tokenize(stream, state); + } + // Handle regex literals + if (stream.match(regexPrefixes)) { + if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division + state.tokenize = tokenFactory(stream.current(), 'string-2'); + return state.tokenize(stream, state); + } else { + stream.backUp(1); + } + } + + // Handle operators and delimiters + if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { + return 'punctuation'; + } + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return 'punctuation'; + } + + if (stream.match(constants)) { + return 'atom'; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + if (stream.match(properties)) { + return 'property'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenFactory(delimiter, outclass) { + var singleline = delimiter.length == 1; + return function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\/\\]/); + if (stream.eat('\\')) { + stream.next(); + if (singleline && stream.eol()) { + return outclass; + } + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return outclass; + } else { + stream.eat(/['"\/]/); + } + } + if (singleline) { + if (conf.mode.singleLineStringErrors) { + outclass = ERRORCLASS + } else { + state.tokenize = tokenBase; + } + } + return outclass; + }; + } + + function longComment(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^#]/); + if (stream.match("###")) { + state.tokenize = tokenBase; + break; + } + stream.eatWhile("#"); + } + return "comment" + } + + function indent(stream, state, type) { + type = type || 'coffee'; + var indentUnit = 0; + if (type === 'coffee') { + for (var i = 0; i < state.scopes.length; i++) { + if (state.scopes[i].type === 'coffee') { + indentUnit = state.scopes[i].offset + conf.indentUnit; + break; + } + } + } else { + indentUnit = stream.column() + stream.current().length; + } + state.scopes.unshift({ + offset: indentUnit, + type: type + }); + } + + function dedent(stream, state) { + if (state.scopes.length == 1) return; + if (state.scopes[0].type === 'coffee') { + var _indent = stream.indentation(); + var _indent_index = -1; + for (var i = 0; i < state.scopes.length; ++i) { + if (_indent === state.scopes[i].offset) { + _indent_index = i; + break; + } + } + if (_indent_index === -1) { + return true; + } + while (state.scopes[0].offset !== _indent) { + state.scopes.shift(); + } + return false + } else { + state.scopes.shift(); + return false; + } + } + + function tokenLexer(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = state.tokenize(stream, state); + current = stream.current(); + if (style === 'variable') { + return 'variable'; + } else { + return ERRORCLASS; + } + } + + // Handle scope changes. + if (current === 'return') { + state.dedent += 1; + } + if (((current === '->' || current === '=>') && + !state.lambda && + state.scopes[0].type == 'coffee' && + stream.peek() === '') + || style === 'indent') { + indent(stream, state); + } + var delimiter_index = '[({'.indexOf(current); + if (delimiter_index !== -1) { + indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); + } + if (indentKeywords.exec(current)){ + indent(stream, state); + } + if (current == 'then'){ + dedent(stream, state); + } + + + if (style === 'dedent') { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + delimiter_index = '])}'.indexOf(current); + if (delimiter_index !== -1) { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') { + if (state.scopes.length > 1) state.scopes.shift(); + state.dedent -= 1; + } + + return style; + } + + var external = { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + scopes: [{offset:basecolumn || 0, type:'coffee'}], + lastToken: null, + lambda: false, + dedent: 0 + }; + }, + + token: function(stream, state) { + var style = tokenLexer(stream, state); + + state.lastToken = {style:style, content: stream.current()}; + + if (stream.eol() && stream.lambda) { + state.lambda = false; + } + + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) { + return 0; + } + + return state.scopes[0].offset; + } + + }; + return external; +}); + +CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript'); diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/index.html b/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/index.html new file mode 100644 index 0000000..98bd497 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/coffeescript/index.html @@ -0,0 +1,727 @@ + + + +text/x-clojure
.CodeMirror: CoffeeScript mode + + + + + + + +CodeMirror: CoffeeScript mode
+ + + +MIME types defined:
+ +text/x-coffeescript
.The CoffeeScript mode was written by Jeff Pickhardt (license).
+ + + diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/css/css.js b/IPython/frontend/html/notebook/static/codemirror/mode/css/css.js old mode 100755 new mode 100644 index 45170a3..050e112 --- a/IPython/frontend/html/notebook/static/codemirror/mode/css/css.js +++ b/IPython/frontend/html/notebook/static/codemirror/mode/css/css.js @@ -92,7 +92,7 @@ CodeMirror.defineMode("css", function(config) { var style = state.tokenize(stream, state); var context = state.stack[state.stack.length-1]; - if (type == "hash" && context == "rule") style = "atom"; + if (type == "hash" && context != "rule") style = "string-2"; else if (style == "variable") { if (context == "rule") style = "number"; else if (!context || context == "@media{") style = "tag"; diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/css/index.html b/IPython/frontend/html/notebook/static/codemirror/mode/css/index.html old mode 100755 new mode 100644 diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/diff/diff.js b/IPython/frontend/html/notebook/static/codemirror/mode/diff/diff.js new file mode 100644 index 0000000..3402f3b --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/diff/diff.js @@ -0,0 +1,32 @@ +CodeMirror.defineMode("diff", function() { + + var TOKEN_NAMES = { + '+': 'tag', + '-': 'string', + '@': 'meta' + }; + + return { + token: function(stream) { + var tw_pos = stream.string.search(/[\t ]+?$/); + + if (!stream.sol() || tw_pos === 0) { + stream.skipToEnd(); + return ("error " + ( + TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, ''); + } + + var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd(); + + if (tw_pos === -1) { + stream.skipToEnd(); + } else { + stream.pos = tw_pos; + } + + return token_name; + } + }; +}); + +CodeMirror.defineMIME("text/x-diff", "diff"); diff --git a/IPython/frontend/html/notebook/static/codemirror/mode/diff/index.html b/IPython/frontend/html/notebook/static/codemirror/mode/diff/index.html new file mode 100644 index 0000000..b102c09 --- /dev/null +++ b/IPython/frontend/html/notebook/static/codemirror/mode/diff/index.html @@ -0,0 +1,104 @@ + + + +CodeMirror: Diff mode + + + + + + + +CodeMirror: Diff mode
+ + + +MIME types defined:
+ + +text/x-diff
.