From d91c9953ee5c8c0b9cb4e431045c6b83931a55c6 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 15 Nov 2012 05:10:10 -0500 Subject: [PATCH] fixed bug with keys panel and fm bottom margin --- ChangeLog | 1 + css/style.css | 2 +- lib/client/editor/codemirror3/codemirror.css | 10 +- lib/client/editor/codemirror3/codemirror.js | 1030 +++++++++-------- .../editor/codemirror3/mode/javascript.js | 78 +- lib/client/editor/codemirror3/package.json | 2 +- 6 files changed, 650 insertions(+), 473 deletions(-) diff --git a/ChangeLog b/ChangeLog index fc8f5d87..bbcd31f4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -87,6 +87,7 @@ dom.js and util.js. * CodeMirror upgraded to version 2.35.0. +* Fixed bug with keys panel and fm bottom margin. 2012.10.01, Version 0.1.7 diff --git a/css/style.css b/css/style.css index 9ab14010..d64dc79c 100644 --- a/css/style.css +++ b/css/style.css @@ -175,7 +175,7 @@ body{ } #fm{ height: 90%; - margin: 26px; + margin: 26px 26px 0 26px; } .fm_header{ font-weight: bold; diff --git a/lib/client/editor/codemirror3/codemirror.css b/lib/client/editor/codemirror3/codemirror.css index 9de98196..9be4b005 100644 --- a/lib/client/editor/codemirror3/codemirror.css +++ b/lib/client/editor/codemirror3/codemirror.css @@ -175,6 +175,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} color: inherit; z-index: 2; position: relative; + overflow: visible; } .CodeMirror-wrap pre { word-wrap: break-word; @@ -187,6 +188,11 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} z-index: 0; } +.CodeMirror-linewidget { + position: relative; + z-index: 2; +} + .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; } @@ -209,8 +215,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} visibility: visible; } -div.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } .CodeMirror-searching { background: #ffa; diff --git a/lib/client/editor/codemirror3/codemirror.js b/lib/client/editor/codemirror3/codemirror.js index 3ebd1807..a9c7412e 100644 --- a/lib/client/editor/codemirror3/codemirror.js +++ b/lib/client/editor/codemirror3/codemirror.js @@ -1,3 +1,5 @@ +// CodeMirror version 3.0beta2 +// // CodeMirror is the only global var we claim window.CodeMirror = (function() { "use strict"; @@ -10,12 +12,14 @@ window.CodeMirror = (function() { 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 ie_lt10 = /MSIE [1-9]\b/.test(navigator.userAgent); 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); + var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); @@ -41,16 +45,14 @@ window.CodeMirror = (function() { if (options.autofocus) focusInput(this); if (options.lineWrapping) display.wrapper.className += " CodeMirror-wrap"; - var doc = new BranchChunk([new LeafChunk([new Line("", null, textHeight(display))])]); - // frontier is the point up to which the content has been parsed, - doc.frontier = 0; - doc.highlight = new Delayed(); - doc.tabSize = options.tabSize; + var doc = new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])]); // The selection. These are always maintained to point at valid // positions. Inverted is used to remember that the user is // selecting bottom-to-top. this.view = { doc: doc, + // frontier is the point up to which the content has been parsed, + frontier: 0, highlight: new Delayed(), sel: {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false, shift: false}, scrollTop: 0, scrollLeft: 0, overwrite: false, focused: false, @@ -66,7 +68,10 @@ window.CodeMirror = (function() { // Initialize the content. this.setValue(options.value || ""); - doc.history = new History(); + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie) setTimeout(bind(resetInput, this, true), 20); + this.view.history = makeHistory(); registerEventHandlers(this); // IE throws unspecified error in certain cases, when @@ -78,6 +83,7 @@ window.CodeMirror = (function() { for (var opt in optionHandlers) if (optionHandlers.propertyIsEnumerable(opt)) optionHandlers[opt](this, options[opt]); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); } // DISPLAY CONSTRUCTOR @@ -102,7 +108,7 @@ window.CodeMirror = (function() { // Used to measure text size d.measure = elt("div", null, "CodeMirror-measure"); // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.cursor, d.otherCursor, d.selectionDiv, d.lineDiv], + d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], null, "position: relative; outline: none"); // Moved around its parent to cover visible view d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); @@ -158,6 +164,10 @@ window.CodeMirror = (function() { // string instead of the (large) selection. d.inaccurateSelection = false; + // Used to adjust overwrite behaviour when a paste has been + // detected + d.pasteIncoming = false; + return d; } @@ -167,9 +177,9 @@ window.CodeMirror = (function() { function loadMode(cm) { var doc = cm.view.doc; - doc.mode = CodeMirror.getMode(cm.options, cm.options.mode); + cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode); doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); - doc.frontier = 0; + cm.view.frontier = 0; startWorker(cm, 100); } @@ -289,7 +299,7 @@ window.CodeMirror = (function() { } else d.scrollbarFiller.style.display = ""; if (mac_geLion && scrollbarWidth(d.measure) === 0) - d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = "12px"; + d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; } function visibleLines(display, doc, scrollTop) { @@ -342,7 +352,7 @@ window.CodeMirror = (function() { if (updated) { signalLater(cm, cm, "update", cm); if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) - signalLater(cm, cm, "update", cm, cm.display.showingFrom, cm.display.showingTo); + signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); } updateSelection(cm); updateScrollbars(cm.display, cm.view.doc.height, scrollTop); @@ -377,7 +387,8 @@ window.CodeMirror = (function() { for (var i = 0; i < changes.length; ++i) if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } - var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + var from = Math.max(visible.from - cm.options.viewportMargin, 0); + var to = Math.min(doc.size, visible.to + cm.options.viewportMargin); if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom; if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo); @@ -398,7 +409,7 @@ window.CodeMirror = (function() { return; intact.sort(function(a, b) {return a.domStart - b.domStart;}); - display.lineDiv.style.display = "none"; + if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; patchDisplay(cm, from, to, intact, positionsChangedFrom); display.lineDiv.style.display = ""; @@ -419,22 +430,19 @@ window.CodeMirror = (function() { // Update line heights for visible lines based on actual DOM // sizes - var curNode = display.lineDiv.firstChild, heightChanged = false; - var relativeTo = curNode.offsetTop; + var curNode = display.lineDiv.firstChild, relativeTo = curNode.offsetTop; doc.iter(display.showingFrom, display.showingTo, function(line) { // Work around bizarro IE7 bug where, sometimes, our curNode // is magically replaced with a new node in the DOM, leaving // us with a reference to an orphan (nextSibling-less) node. if (!curNode) return; - if (!line.hidden) { + if (!line.hidden || (line.hidden.handle.showWidgets && line.widgets)) { var end = curNode.offsetHeight + curNode.offsetTop; var height = end - relativeTo, diff = line.height - height; if (height < 2) height = textHeight(display); relativeTo = end; - if (diff > .001 || diff < -.001) { + if (diff > .001 || diff < -.001) updateLineHeight(line, height); - heightChanged = true; - } } curNode = curNode.nextSibling; }); @@ -467,16 +475,34 @@ window.CodeMirror = (function() { return intact; } + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft; + width[cm.options.gutters[i]] = n.offsetWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + function patchDisplay(cm, from, to, intact, updateNumbersFrom) { function killNode(node) { var tmp = node.nextSibling; node.parentNode.removeChild(node); return tmp; } + var dims = getDimensions(cm); var display = cm.display, lineNumbers = cm.options.lineNumbers; // The first pass removes the DOM nodes that aren't intact. - if (!intact.length) removeChildren(display.lineDiv); - else { + if (!intact.length) { + // old IE does bad things to nodes when .innerHTML = "" is used on a parent + // we still need widgets and markers intact to add back to the new content later + if (ie_lt10) for (var ld = display.lineDiv, tmp = ld.firstChild; tmp; tmp = ld.firstChild) ld.removeChild(tmp); + else removeChildren(display.lineDiv); + } else { var domPos = 0, curNode = display.lineDiv.firstChild, n; for (var i = 0; i < intact.length; ++i) { var cur = intact[i]; @@ -491,79 +517,84 @@ window.CodeMirror = (function() { } // This pass fills in the lines that actually changed. var nextIntact = intact.shift(), curNode = display.lineDiv.firstChild; - var j = from, gutterSpecs = cm.options.gutters; - var fixedPos = compensateForHScroll(display); + var j = from; cm.view.doc.iter(from, to, function(line) { if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); - if (!nextIntact || nextIntact.from > j) { - if (line.hidden) var lineElement = elt("div"); - else { - var lineElement = lineContent(cm, line), markers = line.gutterMarkers; - if (line.className) lineElement.className = line.className; - // Lines with gutter elements or a background class need - // to be wrapped again, and have the extra elements added - - // to the wrapper div - if (lineNumbers || markers || line.bgClassName || (line.widgets && line.widgets.length)) { - var wrap = elt("div", null, null, "position: relative"); - if (lineNumbers || markers) { - var gutterWrap = wrap.appendChild(elt("div", null, null, - "position: absolute; left: " + fixedPos + "px")); - wrap.alignable = [gutterWrap]; - if (lineNumbers) - gutterWrap.appendChild(elt("div", lineNumberFor(cm.options, j), - "CodeMirror-linenumber CodeMirror-gutter-elt", - "left: " + display.lineGutter.offsetLeft + "px; width: " - + display.lineNumInnerWidth + "px")); - if (markers) - for (var k = 0; k < gutterSpecs.length; ++k) { - var id = gutterSpecs[k], found = markers.hasOwnProperty(id) && markers[id]; - if (found) { - var gutterElt = display.gutters.childNodes[k]; - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + - gutterElt.offsetLeft + "px; width: " + gutterElt.clientWidth + "px")); - } - } - } - // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClassName) - wrap.appendChild(elt("div", "\u00a0", line.bgClassName + " CodeMirror-linebackground")); - wrap.appendChild(lineElement); - if (line.widgets) - for (var i = 0, ws = line.widgets; i < ws.length; ++i) { - var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); - node.widget = widget; - node.style.position = "relative"; - if (widget.noHScroll) { - (wrap.alignable || (wrap.alignable = [])).push(node); - var width = display.wrapper.clientWidth; - node.style.left = fixedPos + "px"; - if (!widget.coverGutter) { - width -= display.gutters.offsetWidth; - node.style.paddingLeft = display.gutters.offsetWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) node.style.marginLeft = -display.gutters.offsetWidth + "px"; - } - wrap.appendChild(node); - } - lineElement = wrap; - if (ie_lt8) lineElement.style.zIndex = 2; - } - } - display.lineDiv.insertBefore(lineElement, curNode); - } else { + if (!nextIntact || nextIntact.from > j) + display.lineDiv.insertBefore(buildLineElement(cm, line, j, dims), curNode); + else curNode = curNode.nextSibling; - } ++j; }); } + function buildLineElement(cm, line, lineNo, dims) { + if (line.hidden && (!line.hidden.handle.showWidgets || !line.widgets)) + return elt("div"); + + var lineElement = line.hidden ? elt("div") : lineContent(cm, line); + var markers = line.gutterMarkers, display = cm.display; + + if (!cm.options.lineNumbers && !markers && !line.bgClassName && + (!line.widgets || !line.widgets.length)) return lineElement; + + // Lines with gutter elements or a background class need + // to be wrapped again, and have the extra elements added + // to the wrapper div + + var wrap = elt("div", null, null, "position: relative"); + if (cm.options.lineNumbers || markers) { + var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " + + dims.fixedPos + "px")); + wrap.alignable = [gutterWrap]; + if (cm.options.lineNumbers) + gutterWrap.appendChild(elt("div", lineNumberFor(cm.options, lineNo), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + display.lineNumInnerWidth + "px")); + if (markers) + for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) { + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + // Kludge to make sure the styled element lies behind the selection (by z-index) + if (line.bgClassName) + wrap.appendChild(elt("div", "\u00a0", line.bgClassName + " CodeMirror-linebackground")); + wrap.appendChild(lineElement); + if (line.widgets) + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + node.widget = widget; + if (widget.noHScroll) { + (wrap.alignable || (wrap.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + if (widget.above) + wrap.insertBefore(node, cm.options.lineNumbers && !line.hidden ? gutterWrap : lineElement); + else + wrap.appendChild(node); + } + + if (ie_lt8) wrap.style.zIndex = 2; + return wrap; + } + // SELECTION / CURSOR function selHead(view) { @@ -576,7 +607,7 @@ window.CodeMirror = (function() { var pos = headPos = cursorCoords(cm, sel.from, "div"); display.cursor.style.left = pos.left + "px"; display.cursor.style.top = pos.top + "px"; - display.cursor.style.height = (pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; display.cursor.style.display = ""; display.selectionDiv.style.display = "none"; @@ -589,7 +620,7 @@ window.CodeMirror = (function() { } else { headPos = cursorCoords(cm, selHead(cm.view), "div"); var fragment = document.createDocumentFragment(); - var clientWidth = display.lineSpace.clientWidth; + var clientWidth = display.lineSpace.offsetWidth; var add = function(left, top, width, bottom) { if (top < 0) top = 0; fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + @@ -669,32 +700,38 @@ window.CodeMirror = (function() { // HIGHLIGHT WORKER function startWorker(cm, time) { - if (cm.view.doc.frontier < cm.display.showingTo) - cm.view.doc.highlight.set(time, bind(highlightWorker, cm)); + if (cm.view.frontier < cm.display.showingTo) + cm.view.highlight.set(time, bind(highlightWorker, cm)); } function highlightWorker(cm) { - var doc = cm.view.doc; - if (doc.frontier >= cm.display.showingTo) return; + var view = cm.view, doc = view.doc; + if (view.frontier >= cm.display.showingTo) return; var end = +new Date + cm.options.workTime; - var state = copyState(doc.mode, getStateBefore(doc, doc.frontier)); - var startFrontier = doc.frontier; - doc.iter(doc.frontier, cm.display.showingTo, function(line) { - if (doc.frontier >= cm.display.showingFrom) { // Visible - line.highlight(doc.mode, state, cm.options.tabSize); - line.stateAfter = copyState(doc.mode, state); + var state = copyState(view.mode, getStateBefore(cm, view.frontier)); + var changed = [], prevChange; + doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) { + if (view.frontier >= cm.display.showingFrom) { // Visible + if (highlightLine(cm, line, state) && view.frontier >= cm.display.showingFrom) { + if (prevChange && prevChange.end == view.frontier) prevChange.end++; + else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1}); + } + line.stateAfter = copyState(view.mode, state); } else { - line.process(doc.mode, state, cm.options.tabSize); - line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + processLine(cm, line, state); + line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null; } - ++doc.frontier; + ++view.frontier; if (+new Date > end) { startWorker(cm, cm.options.workDelay); return true; } }); - if (cm.display.showingTo > startFrontier && doc.frontier >= cm.display.showingFrom) - operation(cm, function() {regChange(this, startFrontier, doc.frontier);})(); + if (changed.length) + operation(cm, function() { + for (var i = 0; i < changed.length; ++i) + regChange(this, changed[i].start, changed[i].end); + })(); } // Finds the line to start with when starting a parse. Tries to @@ -702,13 +739,13 @@ window.CodeMirror = (function() { // valid state. If that fails, it returns the line with the // smallest indentation, which tends to need the least context to // parse correctly. - function findStartLine(doc, n) { - var minindent, minline; + function findStartLine(cm, n) { + var minindent, minline, doc = cm.view.doc; for (var search = n, lim = n - 100; search > lim; --search) { if (search == 0) return 0; var line = getLine(doc, search-1); if (line.stateAfter) return search; - var indented = line.indentation(doc.tabSize); + var indented = countColumn(line.text, null, cm.options.tabSize); if (minline == null || minindent > indented) { minline = search - 1; minindent = indented; @@ -717,13 +754,14 @@ window.CodeMirror = (function() { return minline; } - function getStateBefore(doc, n) { - var pos = findStartLine(doc, n), state = pos && getLine(doc, pos-1).stateAfter; - if (!state) state = startState(doc.mode); - else state = copyState(doc.mode, state); - doc.iter(pos, n, function(line) { - line.process(doc.mode, state, doc.tabSize); - line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(doc.mode, state) : null; + function getStateBefore(cm, n) { + var view = cm.view; + var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter; + if (!state) state = startState(view.mode); + else state = copyState(view.mode, state); + view.doc.iter(pos, n, function(line) { + processLine(cm, line, state); + line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(view.mode, state) : null; }); return state; } @@ -750,12 +788,13 @@ window.CodeMirror = (function() { var atEnd = ch && ch == line.text.length; var pre = lineContent(cm, line, atEnd ? ch - 1 : ch); removeChildrenAndAdd(display.measure, pre); - var anchor = pre.anchor, outer = display.lineDiv.getBoundingClientRect(); + var anchor = pre.anchor; // We'll sample once at the top, once at the bottom of the line, // to get the real line height (in case there tokens on the line // with bigger fonts) anchor.style.verticalAlign = "top"; - var box1 = anchor.getBoundingClientRect(), left = box1.left - outer.left, right = box1.right - outer.left; + var outer = display.lineDiv.getBoundingClientRect(), box1 = anchor.getBoundingClientRect(); + var left = box1.left - outer.left, right = box1.right - outer.left; if (ie) { var left1 = anchor.offsetLeft; // In IE, verticalAlign does not influence offsetTop, unless @@ -787,7 +826,11 @@ window.CodeMirror = (function() { } // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" - function intoCoordSystem(cm, pos, rect, context) { + function intoCoordSystem(cm, lineObj, pos, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = lineObj.widgets[i].node.offsetHeight; + rect.top += size; rect.bottom += size; + } if (context == "line") return rect; if (!context) context = "local"; var yOff = heightAtLine(cm.view.doc, pos.line); @@ -804,7 +847,7 @@ window.CodeMirror = (function() { function charCoords(cm, pos, context, lineObj) { if (!lineObj) lineObj = getLine(cm.view.doc, pos.line); - return intoCoordSystem(cm, pos, measureLine(cm, lineObj, pos.ch), context); + return intoCoordSystem(cm, lineObj, pos, measureLine(cm, lineObj, pos.ch), context); } function cursorCoords(cm, pos, context, lineObj) { @@ -812,7 +855,7 @@ window.CodeMirror = (function() { function get(ch, right) { var m = measureLine(cm, lineObj, ch); if (right) m.left = m.right; else m.right = m.left; - return intoCoordSystem(cm, pos, m, context); + return intoCoordSystem(cm, lineObj, pos, m, context); } var order = getOrder(lineObj), ch = pos.ch; if (!order) return get(ch); @@ -847,22 +890,20 @@ window.CodeMirror = (function() { function coordsChar(cm, x, y) { var display = cm.display, doc = cm.view.doc; var cw = charWidth(display), heightPos = display.viewOffset + y; - if (heightPos < 0) return {line: 0, ch: 0}; + if (heightPos < 0) return {line: 0, ch: 0, outside: true}; var lineNo = lineAtHeight(doc, heightPos); if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length}; var lineObj = getLine(doc, lineNo); if (!lineObj.text.length) return {line: lineNo, ch: 0}; - var tw = cm.options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + var innerOff = heightPos - heightAtLine(doc, lineNo); if (x < 0) x = 0; - var wrongLine = false; + var wrongLine = false, cWidth = display.wrapper.clientWidth; function getX(ch) { var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line", lineObj); - if (tw) { - wrongLine = true; - if (innerOff > sp.bottom) return Math.max(0, sp.left - display.wrapper.clientWidth); - else if (innerOff < sp.top) return sp.left + display.wrapper.clientWidth; - else wrongLine = false; - } + wrongLine = true; + if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth); + else if (innerOff < sp.top) return sp.left + cWidth; + else wrongLine = false; return sp.left; } var bidi = getOrder(lineObj), dist = lineObj.text.length; @@ -881,13 +922,13 @@ window.CodeMirror = (function() { if (estX < x) {from = estimated; fromX = estX;} dist = to - from; } else toX = getX(to); - if (x > toX) return {line: lineNo, ch: to}; + if (x > toX) return {line: lineNo, ch: to, outside: wrongLine}; // Do a binary search between these bounds. for (;;) { if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { var after = x - fromX < toX - x, ch = after ? from : to; while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; - return {line: lineNo, ch: ch, after: after}; + return {line: lineNo, ch: ch, after: after, outside: wrongLine}; } var step = Math.ceil(dist / 2), middle = from + step; if (bidi) { @@ -1001,12 +1042,6 @@ window.CodeMirror = (function() { cm.curOp.changes.push({from: from, to: to, diff: lendiff}); } - function compoundChange(cm, f) { - var hist = cm.view.doc.history; - hist.startCompound(); - try { return f(); } finally { hist.endCompound(); } - } - // INPUT HANDLING function slowPoll(cm) { @@ -1044,7 +1079,7 @@ window.CodeMirror = (function() { while (same < l && prevInput[same] == text[same]) ++same; if (same < prevInput.length) sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; - else if (view.overwrite && posEq(sel.from, sel.to)) + else if (view.overwrite && posEq(sel.from, sel.to) && !cm.display.pasteIncoming) sel.to = {line: sel.to.line, ch: Math.min(getLine(cm.view.doc, sel.to.line).text.length, sel.to.ch + (text.length - same))}; var updateInput = cm.curOp.updateInput; cm.replaceSelection(text.slice(same), "end"); @@ -1052,6 +1087,7 @@ window.CodeMirror = (function() { if (text.length > 1000) { input.value = cm.display.prevInput = ""; } else cm.display.prevInput = text; endOperation(cm); + cm.display.pasteIncoming = false; return true; } @@ -1088,29 +1124,20 @@ window.CodeMirror = (function() { if (!gecko) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); on(d.scroller, "scroll", function() { - if (d.scroller.scrollTop != cm.view.scrollTop) { - d.scrollbarV.scrollTop = cm.view.scrollTop = d.scroller.scrollTop; - updateDisplay(cm, []); - } - if (d.scroller.scrollLeft != cm.view.scrollLeft) { - d.scrollbarH.scrollLeft = cm.view.scrollLeft = d.scroller.scrollLeft; - alignVertically(cm.display); - } + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft); signal(cm, "scroll", cm); }); on(d.scrollbarV, "scroll", function() { - if (d.scrollbarV.scrollTop != cm.view.scrollTop) { - d.scroller.scrollTop = cm.view.scrollTop = d.scrollbarV.scrollTop; - updateDisplay(cm, []); - } + setScrollTop(cm, d.scrollbarV.scrollTop); }); on(d.scrollbarH, "scroll", function() { - if (d.scrollbarH.scrollLeft != cm.view.scrollLeft) { - d.scroller.scrollLeft = cm.view.scrollLeft = d.scrollbarH.scrollLeft; - alignVertically(cm.display); - } + setScrollLeft(cm, d.scrollbarH.scrollLeft); }); + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + function reFocus() { if (cm.view.focused) setTimeout(bind(focusInput, cm), 0); } on(d.scrollbarH, "mousedown", reFocus); on(d.scrollbarV, "mousedown", reFocus); @@ -1145,7 +1172,11 @@ window.CodeMirror = (function() { on(d.scroller, "drop", operation(cm, onDrop)); } on(d.scroller, "paste", function(){focusInput(cm); fastPoll(cm);}); - on(d.input, "paste", bind(fastPoll, cm)); + on(d.input, "paste", function() { + d.pasteIncoming = true; + fastPoll(cm); + }); + function prepareCopy() { if (d.inaccurateSelection) { d.prevInput = ""; @@ -1246,7 +1277,7 @@ window.CodeMirror = (function() { }); // Let the drag handler handle this. if (webkit) display.scroller.draggable = true; - view.draggingText = true; + view.draggingText = dragEnd; // IE's approach to draggable if (display.scroller.dragDrop) display.scroller.dragDrop(); on(document, "mouseup", dragEnd); @@ -1344,7 +1375,11 @@ window.CodeMirror = (function() { for (var i = 0; i < n; ++i) loadFile(files[i], i); } else { // Don't do a replace if the drop happened inside of the selected text. - if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) return; + if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) { + cm.view.draggingText(e); + if (ie) setTimeout(bind(focusInput, cm), 50); + return; + } try { var text = e.dataTransfer.getData("Text"); if (text) { @@ -1354,6 +1389,7 @@ window.CodeMirror = (function() { if (cm.view.draggingText) replaceRange(cm, "", curFrom, curTo); cm.replaceSelection(text); focusInput(cm); + onFocus(cm); }); } } @@ -1395,6 +1431,68 @@ window.CodeMirror = (function() { e.dataTransfer.setDragImage(elt('img'), 0, 0); } + function setScrollTop(cm, val) { + if (cm.view.scrollTop == val) return; + cm.view.scrollTop = val; + if (!gecko) updateDisplay(cm, [], val); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; + if (gecko) updateDisplay(cm, []); + } + function setScrollLeft(cm, val) { + if (cm.view.scrollLeft == val) return; + cm.view.scrollLeft = val; + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; + alignVertically(cm.display); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to directly handle the wheel event is that it + // gives us a chance to update the display before the actual + // scrolling happens, reducing flickering. + + var wheelSamples = [], wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit; + function onScrollWheel(cm, e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + + var scroll = cm.display.scroller; + if (wheelPixelsPerUnit != null) { + if (dx) setScrollLeft(cm, Math.max(0, Math.round(scroll.scrollLeft += dx * wheelPixelsPerUnit))); + if (dy) setScrollTop(cm, Math.max(0, Math.round(scroll.scrollTop + dy * wheelPixelsPerUnit))); + e_stop(e); + } else { + if (wheelStartX == null) { + wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop; + wheelDX = dx; wheelDY = dy; + setTimeout(function() { + var movedX = scroll.scrollLeft - wheelStartX; + var movedY = scroll.scrollTop - wheelStartY; + var sample = (movedY && wheelDY && movedY / wheelDY) || + (movedX && wheelDX && movedX / wheelDX); + wheelStartX = wheelStartY = null; + if (!sample) return; + wheelSamples.push(sample); + if (wheelSamples.length >= 15) { + for (var total = 0, i = 0; i < wheelSamples.length; ++i) total += wheelSamples[i]; + wheelPixelsPerUnit = total / wheelSamples.length; + } + }, 200); + } else { + wheelDX += dx; wheelDY += dy; + } + } + } + function doHandleBinding(cm, bound, dropShift) { if (typeof bound == "string") { bound = commands[bound]; @@ -1492,9 +1590,9 @@ window.CodeMirror = (function() { if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (this.options.electricChars && this.view.doc.mode.electricChars && + if (this.options.electricChars && this.view.mode.electricChars && this.options.smartIndent && !this.options.readOnly && - this.view.doc.mode.electricChars.indexOf(ch) > -1) + this.view.mode.electricChars.indexOf(ch) > -1) setTimeout(operation(cm, function() {indentLine(cm, cm.view.sel.to.line, "smart");}), 75); if (handleCharBinding(cm, e, ch)) return; fastPoll(cm); @@ -1582,9 +1680,9 @@ window.CodeMirror = (function() { doc.iter(from.line, to.line + 1, function(line) { old.push(newHL(line.text, line.markedSpans)); }); - if (doc.history) { - doc.history.addChange(from.line, newText.length, old); - while (doc.history.done.length > cm.options.undoDepth) doc.history.done.shift(); + if (view.history) { + addChange(view.history, from.line, newText.length, old); + while (view.history.done.length > cm.options.undoDepth) view.history.done.shift(); } var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); updateDocNoUndo(cm, from, to, lines, selFrom, selTo); @@ -1606,11 +1704,11 @@ window.CodeMirror = (function() { to.push(out); } function undo(cm) { - var hist = cm.view.doc.history; + var hist = cm.view.history; unredoHelper(cm, hist.done, hist.undone); } function redo(cm) { - var hist = cm.view.doc.history; + var hist = cm.view.history; unredoHelper(cm, hist.undone, hist.done); } @@ -1632,31 +1730,31 @@ window.CodeMirror = (function() { // sure line objects move the way they are supposed to. var added = [], prevLine = null; for (var i = 0, e = lines.length - 1; i < e; ++i) - added.push(new Line(hlText(lines[i]), hlSpans(lines[i]), th)); - lastLine.update(lastLine.text, hlSpans(lastHL), cm); + added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); + updateLine(cm, lastLine, lastLine.text, hlSpans(lastHL)); if (nlines) doc.remove(from.line, nlines, cm); if (added.length) doc.insert(from.line, added); } else if (firstLine == lastLine) { if (lines.length == 1) { - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), - hlSpans(lines[0]), cm); + updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) + + firstLine.text.slice(to.ch), hlSpans(lines[0])); } else { for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) - added.push(new Line(hlText(lines[i]), hlSpans(lines[i]), th)); - added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th)); - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]), cm); + added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); + added.push(makeLine(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th)); + updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); doc.insert(from.line + 1, added); } } else if (lines.length == 1) { - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), - hlSpans(lines[0]), cm); + updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) + + lastLine.text.slice(to.ch), hlSpans(lines[0])); doc.remove(from.line + 1, nlines, cm); } else { var added = []; - firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]), cm); - lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL), cm); + updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); + updateLine(cm, lastLine, hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); for (var i = 1, e = lines.length - 1; i < e; ++i) - added.push(new Line(hlText(lines[i]), hlSpans(lines[i]), th)); + added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); if (nlines > 1) doc.remove(from.line + 1, nlines - 1, cm); doc.insert(from.line + 1, added); } @@ -1680,7 +1778,7 @@ window.CodeMirror = (function() { } // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line); + view.frontier = Math.min(view.frontier, from.line); startWorker(cm, 400); var lendiff = lines.length - nlines - 1; @@ -1878,18 +1976,19 @@ window.CodeMirror = (function() { var doc = cm.view.doc; if (!how) how = "add"; if (how == "smart") { - if (!doc.mode.indent) how = "prev"; - else var state = getStateBefore(doc, n); + if (!cm.view.mode.indent) how = "prev"; + else var state = getStateBefore(cm, n); } - var line = getLine(doc, n), curSpace = line.indentation(doc.tabSize), - curSpaceString = line.text.match(/^\s*/)[0], indentation; + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + var curSpaceString = line.text.match(/^\s*/)[0], indentation; if (how == "smart") { - indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + indentation = cm.view.mode.indent(state, line.text.slice(curSpaceString.length), line.text); if (indentation == Pass) how = "prev"; } if (how == "prev") { - if (n) indentation = getLine(doc, n-1).indentation(doc.tabSize); + if (n) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); else indentation = 0; } else if (how == "add") indentation = curSpace + cm.options.indentUnit; @@ -1899,11 +1998,12 @@ window.CodeMirror = (function() { var indentString = "", pos = 0; if (cm.options.indentWithTabs) - for (var i = Math.floor(indentation / doc.tabSize); i; --i) {pos += doc.tabSize; indentString += "\t";} + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} if (pos < indentation) indentString += spaceStr(indentation - pos); if (indentString != curSpaceString) replaceRange(cm, indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + line.stateAfter = null; } function changeLine(cm, handle, op) { @@ -1922,7 +2022,7 @@ window.CodeMirror = (function() { function findNextLine() { for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { var lo = getLine(doc, l); - if (!lo.hidden || !lo.hidden.handle.unfoldOnEneter) { line = l; lineObj = lo; return true; } + if (!lo.hidden || lo.hidden.handle.unfoldOnEnter) { line = l; lineObj = lo; return true; } } } function moveOnce(boundToLine) { @@ -2006,9 +2106,11 @@ window.CodeMirror = (function() { else if (option == "readOnly" && !value) {resetInput(this, true);} else if (option == "theme") themeChanged(this); else if (option == "lineWrapping") operation(this, wrappingChanged)(this); - else if (option == "tabSize") {this.view.doc.tabSize = value; updateDisplay(this, true);} + else if (option == "tabSize") updateDisplay(this, true); else if (option == "keyMap") keyMapChanged(this); else if (option == "gutters" || option == "lineNumbers") setGuttersForLineNumbers(this.options); + else if (option == "tabindex") this.display.input.tabIndex = value; + else if (option == "viewportMargin") this.refresh(); if (option == "lineNumbers" || option == "gutters" || option == "firstLineNumber" || option == "theme" || option == "lineNumberFormatter") guttersChanged(this); @@ -2018,14 +2120,14 @@ window.CodeMirror = (function() { getOption: function(option) {return this.options[option];}, - getMode: function() {return this.view.doc.mode;}, + getMode: function() {return this.view.mode;}, undo: operation(null, function() { - var hist = this.view.doc.history; + var hist = this.view.history; unredoHelper(this, hist.done, hist.undone); }), redo: operation(null, function() { - var hist = this.view.doc.history; + var hist = this.view.history; unredoHelper(this, hist.undone, hist.done); }), @@ -2045,14 +2147,14 @@ window.CodeMirror = (function() { }), historySize: function() { - var hist = this.view.doc.history; + var hist = this.view.history; return {undo: hist.done.length, redo: hist.undone.length}; }, - clearHistory: function() {this.view.doc.history = new History();}, + clearHistory: function() {this.view.history = makeHistory();}, getHistory: function() { - var hist = this.view.doc.history; + var hist = this.view.history; function cp(arr) { for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { nw.push(nwelt = []); @@ -2068,7 +2170,7 @@ window.CodeMirror = (function() { }, setHistory: function(histData) { - var hist = this.view.doc.history = new History(); + var hist = this.view.history = makeHistory(); hist.done = histData.done; hist.undone = histData.undone; }, @@ -2076,14 +2178,13 @@ window.CodeMirror = (function() { getTokenAt: function(pos) { var doc = this.view.doc; pos = clipPos(doc, pos); - return getLine(doc, pos.line).getTokenAt(doc.mode, getStateBefore(doc, pos.line), - this.options.tabSize, pos.ch); + return getTokenAt(this, getLine(doc, pos.line), getStateBefore(this, pos.line), pos.ch); }, getStateAfter: function(line) { var doc = this.view.doc; line = clipLine(doc, line == null ? doc.size - 1: line); - return getStateBefore(doc, line + 1); + return getStateBefore(this, line + 1); }, cursorCoords: function(start, mode) { @@ -2114,7 +2215,7 @@ window.CodeMirror = (function() { var span = {from: curLine == from.line ? from.ch : null, to: curLine == to.line ? to.ch : null, marker: marker}; - (line.markedSpans || (line.markedSpans = [])).push(span); + line.markedSpans = (line.markedSpans || []).concat([span]); marker.lines.push(line); ++curLine; }); @@ -2127,7 +2228,7 @@ window.CodeMirror = (function() { pos = clipPos(doc, pos); var marker = new TextMarker(this, "bookmark"), line = getLine(doc, pos.line); var span = {from: pos.ch, to: pos.ch, marker: marker}; - (line.markedSpans || (line.markedSpans = [])).push(span); + line.markedSpans = (line.markedSpans || []).concat([span]); marker.lines.push(line); return marker; }, @@ -2195,11 +2296,12 @@ window.CodeMirror = (function() { regChange(this, no, no + 1); }), - foldLines: operation(null, function(from, to, unfoldOnEnter) { + foldLines: operation(null, function(from, to, options) { if (typeof from != "number") from = lineNo(from); if (typeof to != "number") to = lineNo(to); if (from > to) return; - var lines = [], handle = {lines: lines, unfoldOnEnter: unfoldOnEnter}, cm = this, view = cm.view, doc = view.doc; + var handle = options || {}, lines = handle.lines = []; + var cm = this, view = cm.view, doc = view.doc; doc.iter(from, to, function(line) { lines.push(line); if (!line.hidden && line.text.length == cm.view.maxLine.text.length) @@ -2373,14 +2475,11 @@ window.CodeMirror = (function() { } else if (unit == "line") { y = dir > 0 ? pos.bottom + 3 : pos.top - 3; } - var target = coordsChar(this, x, y), line; - // Work around problem with moving 'through' line widgets - if (dir > 0 && target.line == cur.line && cur.line < doc.size - 1 && getLine(doc, cur.line).widgets && - Math.abs(cursorCoords(this, target, "div").top - pos.top) < 2) - target = coordsChar(this, x, cursorCoords(this, {line: cur.line + 1, ch: 0}, "div").top + 3); - else if (dir < 0 && cur.line > 0 && (line = getLine(doc, target.line)).widgets && target.ch == line.text.length) - target = coordsChar(this, x, cursorCoords(this, {line: target.line, ch: line.text.length}, "div").bottom - 3); - + do { + var target = coordsChar(this, x, y); + y += dir * 5; + } while (target.outside && (dir < 0 ? y > 0 : y < doc.height)); + if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top; setSelectionUser(this, target, target); view.goalColumn = x; @@ -2480,8 +2579,10 @@ window.CodeMirror = (function() { cursorHeight: 1, workTime: 100, workDelay: 200, + flattenSpans: true, pollInterval: 100, undoDepth: 40, + viewportMargin: 10, tabindex: null, autofocus: null, lineNumberFormatter: function(integer) { return integer; } @@ -2550,6 +2651,9 @@ window.CodeMirror = (function() { optionHandlers[name] = handler; }; + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + // MODE STATE HANDLING // Utility functions for working with state. Exported because modes @@ -2719,6 +2823,7 @@ window.CodeMirror = (function() { var name = keyNames[e_prop(event, "keyCode")]; return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; } + CodeMirror.isModifierKey = isModifierKey; // FROMTEXTAREA @@ -2844,17 +2949,17 @@ window.CodeMirror = (function() { TextMarker.prototype.clear = function() { startOperation(this.cm); - var min = Infinity, max = -Infinity; + var min = null, max = null; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this, true); - if (span.from != null || span.to != null) { - var lineN = lineNo(line); - min = Math.min(min, lineN); max = Math.max(max, lineN); - } + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) min = lineNo(line); + if (span.to != null) max = lineNo(line); + line.markedSpans = removeMarkedSpan(line.markedSpans, span); } - if (min != Infinity) regChange(this.cm, min, max + 1); + if (min != null) regChange(this.cm, min, max + 1); this.lines.length = 0; + this.explicitlyCleared = true; endOperation(this.cm); }; @@ -2875,15 +2980,17 @@ window.CodeMirror = (function() { // TEXTMARKER SPANS - function getMarkedSpanFor(spans, marker, del) { + function getMarkedSpanFor(spans, marker) { if (spans) for (var i = 0; i < spans.length; ++i) { var span = spans[i]; - if (span.marker == marker) { - if (del) spans.splice(i, 1); - return span; - } + if (span.marker == marker) return span; } } + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } function markedSpansBefore(old, startCh, endCh) { if (old) for (var i = 0, nw; i < old.length; ++i) { @@ -2968,7 +3075,15 @@ window.CodeMirror = (function() { // hl stands for history-line, a data structure that can be either a // string (line without markers) or a {text, markedSpans} object. function hlText(val) { return typeof val == "string" ? val : val.text; } - function hlSpans(val) { return typeof val == "string" ? null : val.markedSpans; } + function hlSpans(val) { + if (typeof val == "string") return null; + var spans = val.markedSpans, out = null; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } function detachMarkedSpans(line) { @@ -2993,208 +3108,218 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function Line(text, markedSpans, height) { - this.text = text; - this.height = height; - attachMarkedSpans(this, markedSpans); + function makeLine(text, markedSpans, height) { + var line = {text: text, height: height}; + attachMarkedSpans(line, markedSpans); + return line; } - Line.prototype = { - update: function(text, markedSpans, cm) { - this.text = text; - this.stateAfter = this.styles = null; - detachMarkedSpans(this); - attachMarkedSpans(this, markedSpans); - signalLater(cm, this, "change"); - }, + function updateLine(cm, line, text, markedSpans) { + line.text = text; + line.stateAfter = line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + signalLater(cm, line, "change"); + } - // Run the given mode's parser over a line, update the styles - // array, which contains alternating fragments of text and CSS - // classes. - highlight: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); - var pos = st.length = 0; - if (this.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol()) { - var style = mode.token(stream, state), substr = stream.current(); - stream.start = stream.pos; - if (pos && st[pos-1] == style) { - st[pos-2] += substr; - } else if (substr) { - st[pos++] = substr; st[pos++] = style; + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + function highlightLine(cm, line, state) { + var mode = cm.view.mode, flattenSpans = cm.options.flattenSpans; + var changed = !line.styles, pos = 0, curText = "", curStyle = null; + var stream = new StringStream(line.text, cm.options.tabSize), st = line.styles || (line.styles = []); + if (line.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state), substr = stream.current(); + stream.start = stream.pos; + if (!flattenSpans || curStyle != style) { + if (curText) { + changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1]; + st[pos++] = curText; st[pos++] = curStyle; } - // Give up when line is ridiculously long - if (stream.pos > 5000) { - st[pos++] = this.text.slice(stream.pos); st[pos++] = null; - break; - } - } - }, - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. - process: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize); - if (this.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol() && stream.pos <= 5000) { - mode.token(stream, state); - stream.start = stream.pos; - } - }, - - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function(mode, state, tabSize, ch) { - var txt = this.text, stream = new StringStream(txt, tabSize); - while (stream.pos < ch && !stream.eol()) { - stream.start = stream.pos; - var style = mode.token(stream, state); - } - return {start: stream.start, - end: stream.pos, - string: stream.current(), - className: style || null, - state: state}; - }, - - indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, - - // Produces an HTML fragment for the line, taking selection, - // marking, and highlighting into account. - getContent: function(tabSize, wrapAt, compensateForWrapping) { - var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; - var pre = elt("pre"); - function span_(text, style) { - if (!text) return; - // Work around a bug where, in some compat modes, IE ignores leading spaces - if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); - first = false; - if (!specials.test(text)) { - col += text.length; - var content = document.createTextNode(text); - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - specials.lastIndex = pos; - var m = specials.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); - col += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabWidth = tabSize - col % tabSize; - content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - col += tabWidth; - } else { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + m[0].charCodeAt(0).toString(16); - content.appendChild(token); - col += 1; - } - } - } - if (style != null) return pre.appendChild(elt("span", [content], style)); - else return pre.appendChild(content); - } - var span = span_; - if (wrapAt != null) { - var outPos = 0; - span = function(text, style) { - var l = text.length; - if (wrapAt >= outPos && wrapAt < outPos + l) { - var cut = wrapAt - outPos; - if (wrapAt > outPos) { - span_(text.slice(0, cut), style); - // See comment at the definition of spanAffectsWrapping - if (compensateForWrapping && spanAffectsWrapping.test(text.slice(cut - 1, cut + 1))) - pre.appendChild(elt("wbr")); - } - if (cut + 1 == l) { - pre.anchor = span_(text.slice(cut), style || ""); - wrapAt--; - } else { - var end = cut + 1; - while (isExtendingChar.test(text.charAt(end))) ++end; - pre.anchor = span_(text.slice(cut, end), style || ""); - if (compensateForWrapping && spanAffectsWrapping.test(text.slice(cut, end + 1))) - pre.appendChild(elt("wbr")); - span_(text.slice(end), style); - } - outPos += l; - } else { - outPos += l; - span_(text, style); - } - }; - } - - var st = this.styles, allText = this.text, marked = this.markedSpans; - var len = allText.length; - function styleToClass(style) { - if (!style) return null; - return "cm-" + style.replace(/ +/g, " cm-"); - } - if (!allText) { - span("\u00a0"); - } 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 { - marked.sort(function(a, b) { return a.from - b.from; }); - var pos = 0, i = 0, text = "", style, sg = 0; - var nextChange = marked[0].from || 0, marks = [], markpos = 0; - var advanceMarks = function() { - var m; - while (markpos < marked.length && - ((m = marked[markpos]).from == pos || m.from == null)) { - if (m.marker.type == "range") marks.push(m); - ++markpos; - } - nextChange = markpos < marked.length ? marked[markpos].from : Infinity; - for (var i = 0; i < marks.length; ++i) { - var to = marks[i].to; - if (to == null) to = Infinity; - if (to == pos) marks.splice(i--, 1); - else nextChange = Math.min(to, nextChange); - } - }; - var m = 0; - while (pos < len) { - if (nextChange == pos) advanceMarks(); - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - var appliedStyle = style; - for (var j = 0; j < marks.length; ++j) { - var mark = marks[j]; - appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; - if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; - if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; - } - span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); - if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} - pos = end; - } - text = st[i++]; style = styleToClass(st[i++]); - } - } - } - return pre; - }, - - cleanUp: function() { - this.parent = null; - detachMarkedSpans(this); + curText = substr; curStyle = style; + } else curText = curText + substr; + // Give up when line is ridiculously long + if (stream.pos > 5000) break; } - }; + if (curText) { + changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1]; + st[pos++] = curText; st[pos++] = curStyle; + } + if (stream.pos > 5000) st[pos++] = line.text.slice(stream.pos); st[pos++] = null; + if (pos != st.length) { st.length = pos; changed = true; } + return changed; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. + function processLine(cm, line, state) { + var mode = cm.view.mode; + var stream = new StringStream(line.text, cm.options.tabSize); + if (line.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol() && stream.pos <= 5000) { + mode.token(stream, state); + stream.start = stream.pos; + } + } + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + function getTokenAt(cm, line, state, ch) { + var mode = cm.view.mode; + var txt = line.text, stream = new StringStream(txt, cm.options.tabSize); + while (stream.pos < ch && !stream.eol()) { + stream.start = stream.pos; + var style = mode.token(stream, state); + } + return {start: stream.start, + end: stream.pos, + string: stream.current(), + className: style || null, + state: state}; + } + + var styleToClassCache = {}; + function styleToClass(style) { + if (!style) return null; + return styleToClassCache[style] || + (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); + } + + // Produces an HTML fragment for the line, taking selection, + // marking, and highlighting into account. + function buildLineContent(line, tabSize, wrapAt, compensateForWrapping) { + var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; + var pre = elt("pre"); + if (line.className) pre.className = line.className; + 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 (!specials.test(text)) { + col += text.length; + var content = document.createTextNode(text); + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + specials.lastIndex = pos; + var m = specials.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); + col += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabWidth = tabSize - col % tabSize; + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + col += tabWidth; + } else { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + m[0].charCodeAt(0).toString(16); + content.appendChild(token); + col += 1; + } + } + } + if (style != null) return pre.appendChild(elt("span", [content], style)); + else return pre.appendChild(content); + } + var span = span_; + if (wrapAt != null) { + var outPos = 0; + span = function(text, style) { + var l = text.length; + if (wrapAt >= outPos && wrapAt < outPos + l) { + var cut = wrapAt - outPos; + if (wrapAt > outPos) { + span_(text.slice(0, cut), style); + // See comment at the definition of spanAffectsWrapping + if (compensateForWrapping && spanAffectsWrapping.test(text.slice(cut - 1, cut + 1))) + pre.appendChild(elt("wbr")); + } + if (cut + 1 == l) { + pre.anchor = span_(text.slice(cut), style || ""); + wrapAt--; + } else { + var end = cut + 1; + while (isExtendingChar.test(text.charAt(end))) ++end; + pre.anchor = span_(text.slice(cut, end), style || ""); + if (compensateForWrapping && spanAffectsWrapping.test(text.slice(cut, end + 1))) + pre.appendChild(elt("wbr")); + span_(text.slice(end), style); + } + outPos += l; + } else { + outPos += l; + span_(text, style); + } + }; + } + + var st = line.styles, allText = line.text, marked = line.markedSpans; + var len = allText.length; + if (!allText) { + span("\u00a0"); + } 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 { + marked.sort(function(a, b) { return a.from - b.from; }); + var pos = 0, i = 0, text = "", style, sg = 0; + var nextChange = marked[0].from || 0, marks = [], markpos = 0; + var advanceMarks = function() { + var m; + while (markpos < marked.length && + ((m = marked[markpos]).from == pos || m.from == null)) { + if (m.marker.type == "range") marks.push(m); + ++markpos; + } + nextChange = markpos < marked.length ? marked[markpos].from : Infinity; + for (var i = 0; i < marks.length; ++i) { + var to = marks[i].to; + if (to == null) to = Infinity; + if (to == pos) marks.splice(i--, 1); + else nextChange = Math.min(to, nextChange); + } + }; + var m = 0; + while (pos < len) { + if (nextChange == pos) advanceMarks(); + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + var appliedStyle = style; + for (var j = 0; j < marks.length; ++j) { + var mark = marks[j]; + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; + if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; + if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; + } + span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + } + text = st[i++]; style = styleToClass(st[i++]); + } + } + } + return pre; + } // DOCUMENT DATA STRUCTURE @@ -3214,7 +3339,7 @@ window.CodeMirror = (function() { for (var i = at, e = at + n; i < e; ++i) { var line = this.lines[i]; this.height -= line.height; - line.cleanUp(); + cleanUpLine(line); signalLater(cm, line, "delete"); } this.lines.splice(at, n); @@ -3410,51 +3535,48 @@ window.CodeMirror = (function() { function lineContent(cm, line, anchorAt) { if (!line.styles) { - var doc = lineDoc(line); - line.highlight(doc.mode, line.stateAfter = getStateBefore(doc, lineNo(line)), cm.options.tabSize); + highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); } - return line.getContent(cm.options.tabSize, anchorAt, cm.options.lineWrapping); + return buildLineContent(line, cm.options.tabSize, anchorAt, cm.options.lineWrapping); } // HISTORY // The history object 'chunks' changes that are made close together // and at almost the same time into bigger undoable units. - function History() { - this.time = 0; - this.done = []; this.undone = []; - this.compound = 0; - this.closed = false; + function makeHistory() { + return {time: 0, done: [], undone: [], + compound: 0, closed: false}; } - History.prototype = { - addChange: function(start, added, old) { - this.undone.length = 0; - var time = +new Date, cur = lst(this.done), last = cur && lst(cur); - var dtime = time - this.time; - 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 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; + function addChange(history, start, added, old) { + history.undone.length = 0; + var time = +new Date, cur = lst(history.done), last = cur && lst(cur); + var dtime = time - history.time; + + if (cur && !history.closed && history.compound) { + cur.push({start: start, added: added, old: old}); + } else if (dtime > 400 || !last || history.closed || + last.start > start + old.length || last.start + last.added < start) { + history.done.push([{start: start, added: added, old: old}]); + history.closed = false; + } else { + 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); } - }; + history.time = time; + } + + function compoundChange(cm, f) { + var hist = cm.view.history; + if (!hist.compound++) hist.closed = true; + try { return f(); } + finally { if (!--hist.compound) hist.closed = true; } + } // EVENT OPERATORS @@ -3996,7 +4118,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.0 B"; + CodeMirror.version = "3.0 B2"; return CodeMirror; })(); diff --git a/lib/client/editor/codemirror3/mode/javascript.js b/lib/client/editor/codemirror3/mode/javascript.js index e754a047..37f6f873 100644 --- a/lib/client/editor/codemirror3/mode/javascript.js +++ b/lib/client/editor/codemirror3/mode/javascript.js @@ -1,6 +1,9 @@ +// TODO actually recognize syntax of TypeScript constructs + CodeMirror.defineMode("javascript", function(config, parserConfig) { var indentUnit = config.indentUnit; var jsonMode = parserConfig.json; + var isTS = parserConfig.typescript; // Tokenizer @@ -8,7 +11,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function kw(type) {return {type: type, style: "keyword"};} var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); var operator = kw("operator"), atom = {type: "atom", style: "atom"}; - return { + + var jsKeywords = { "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "var": kw("var"), "const": kw("var"), "let": kw("var"), @@ -17,6 +21,35 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { "in": operator, "typeof": operator, "instanceof": operator, "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom }; + + // Extend the 'normal' keywords with the TypeScript language extensions + if (isTS) { + var type = {type: "variable", style: "variable-3"}; + var tsKeywords = { + // object-like things + "interface": kw("interface"), + "class": kw("class"), + "extends": kw("extends"), + "constructor": kw("constructor"), + + // scope modifiers + "public": kw("public"), + "private": kw("private"), + "protected": kw("protected"), + "static": kw("static"), + + "super": kw("super"), + + // types + "string": type, "number": type, "bool": type, "any": type + }; + + for (var attr in tsKeywords) { + jsKeywords[attr] = tsKeywords[attr]; + } + } + + return jsKeywords; }(); var isOperatorChar = /[+\-*&%=<>!?|]/; @@ -66,7 +99,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { stream.skipToEnd(); return ret("comment", "comment"); } - else if (state.reAllowed) { + else if (state.lastType == "operator" || state.lastType == "keyword c" || + /^[\[{}\(,;:]$/.test(state.lastType)) { nextUntilUnescaped(stream, "/"); stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla return ret("regexp", "string-2"); @@ -87,7 +121,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { else { stream.eatWhile(/[\w\$_]/); var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; - return (known && state.kwAllowed) ? ret(known.type, known.style, word) : + return (known && state.lastType != ".") ? ret(known.type, known.style, word) : ret("variable", "variable", word); } } @@ -275,19 +309,30 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "}") return cont(); return pass(statement, block); } + function maybetype(type) { + if (type == ":") return cont(typedef); + return pass(); + } + function typedef(type) { + if (type == "variable"){cx.marked = "variable-3"; return cont();} + return pass(); + } function vardef1(type, value) { - if (type == "variable"){register(value); return cont(vardef2);} - return cont(); + if (type == "variable") { + register(value); + return isTS ? cont(maybetype, vardef2) : cont(vardef2); + } + return pass(); } function vardef2(type, value) { if (value == "=") return cont(expression, vardef2); if (type == ",") return cont(vardef1); } function forspec1(type) { - if (type == "var") return cont(vardef1, forspec2); - if (type == ";") return pass(forspec2); + if (type == "var") return cont(vardef1, expect(";"), forspec2); + if (type == ";") return cont(forspec2); if (type == "variable") return cont(formaybein); - return pass(forspec2); + return cont(forspec2); } function formaybein(type, value) { if (value == "in") return cont(expression); @@ -306,7 +351,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); } function funarg(type, value) { - if (type == "variable") {register(value); return cont();} + if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} } // Interface @@ -315,8 +360,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { startState: function(basecolumn) { return { tokenize: jsTokenBase, - reAllowed: true, - kwAllowed: true, + lastType: null, cc: [], lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), localVars: parserConfig.localVars, @@ -334,19 +378,21 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (type == "comment") return style; - state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); - state.kwAllowed = type != '.'; + state.lastType = type; return parseJS(state, style, type, content, stream); }, indent: function(state, textAfter) { + if (state.tokenize == jsTokenComment) return CodeMirror.Pass; if (state.tokenize != jsTokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; var type = lexical.type, closing = firstChar == type; - if (type == "vardef") return lexical.indented + 4; + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0); else if (type == "form" && firstChar == "{") return lexical.indented; - else if (type == "stat" || type == "form") return lexical.indented + indentUnit; + else if (type == "form") return lexical.indented + indentUnit; + else if (type == "stat") + return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0); else if (lexical.info == "switch" && !closing) return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); else if (lexical.align) return lexical.column + (closing ? 0 : 1); @@ -359,3 +405,5 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { CodeMirror.defineMIME("text/javascript", "javascript"); CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); +CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); +CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); diff --git a/lib/client/editor/codemirror3/package.json b/lib/client/editor/codemirror3/package.json index 7caef588..ea18e6b9 100644 --- a/lib/client/editor/codemirror3/package.json +++ b/lib/client/editor/codemirror3/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"3.0.1", + "version":"3.0.2", "main": "codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT",