diff --git a/.travis.yml b/.travis.yml index 549e24a..c835df7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ node_js: - "0.8" - before_script: + before_install: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - - npm install -g grunt@0.3.x testacular@0.2.x + - npm install -g grunt@0.3.x testacular@0.4.x script: "grunt" \ No newline at end of file diff --git a/grunt.js b/grunt.js index 952fd82..41f1608 100644 --- a/grunt.js +++ b/grunt.js @@ -105,7 +105,7 @@ module.exports = function (grunt) { var done = this.async(); grunt.utils.spawn({ cmd: process.platform === 'win32' ? 'testacular.cmd' : 'testacular', - args: process.env.TRAVIS ? ['start', 'test/test-config.js', '--single-run', '--no-auto-watch', '--reporter=dots', '--browsers=Firefox'] : ['run'] + args: process.env.TRAVIS ? ['start', 'test/test-config.js', '--single-run', '--no-auto-watch', '--reporters=dots', '--browsers=Firefox'] : ['run'] }, function (error, result, code) { if (error) { grunt.warn("Make sure the testacular server is online: run `grunt server`.\n" + diff --git a/modules/directives/codemirror/codemirror.js b/modules/directives/codemirror/codemirror.js index 2c0d0f2..7908a59 100644 --- a/modules/directives/codemirror/codemirror.js +++ b/modules/directives/codemirror/codemirror.js @@ -60,6 +60,14 @@ angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$timeou codeMirror.setValue(ngModel.$viewValue); }; + // Watch ui-refresh and refresh the directive + if (attrs.uiRefresh) { + scope.$watch(attrs.uiRefresh, function(newVal, oldVal){ + // Skip the initial watch firing + if (newVal !== oldVal) + $timeout(codeMirror.refresh); + }); + } }; $timeout(deferCodeMirror); diff --git a/modules/directives/codemirror/test/codemirrorSpec.js b/modules/directives/codemirror/test/codemirrorSpec.js index b10767f..e32f343 100644 --- a/modules/directives/codemirror/test/codemirrorSpec.js +++ b/modules/directives/codemirror/test/codemirrorSpec.js @@ -2,135 +2,157 @@ /** * TODO Test all the CodeMirror events : cursorActivity viewportChange gutterClick focus blur scroll update. * with ')(scope); + } + + expect(compile).not.toThrow(); + }); + + it('should throw an error when no ngModel attribute defined', function () { + function compile() { + $compile('')(scope); + } + + expect(compile).toThrow(); + }); + + it('should watch the uiCodemirror attribute', function () { + spyOn(scope, '$watch'); + $compile('')(scope); + $timeout.flush(); + expect(scope.$watch).toHaveBeenCalled(); + }); + + }); + + describe('while spying on the CodeMirror instance', function () { + + var codemirror; + + beforeEach(function () { + var fromTextArea = CodeMirror.fromTextArea; + spyOn(CodeMirror, 'fromTextArea').andCallFake(function () { + codemirror = fromTextArea.apply(this, arguments); + return codemirror; + }); + }); + + describe('verify the directive options', function () { + it('should include the passed options', function () { + $compile('')(scope); + $timeout.flush(); + expect(CodeMirror.fromTextArea.mostRecentCall.args[1].oof).toEqual("baar"); + }); + + it('should include the default options', function () { + $compile('')(scope); + $timeout.flush(); + expect(CodeMirror.fromTextArea.mostRecentCall.args[1].bar).toEqual('baz'); + }); + }); + + describe('when uiRefresh is added', function () { + it('should trigger the CodeMirror.refresh() method', function () { + $compile('')(scope); + $timeout.flush(); + spyOn(codemirror, 'refresh'); + scope.$apply('bar = true'); + $timeout.flush(); + expect(codemirror.refresh).toHaveBeenCalled(); + }); + }); + + + describe('when the IDE changes', function () { + it('should update the model', function () { + $compile('')(scope); + scope.$apply("foo = 'bar'"); + $timeout.flush(); + var value = 'baz'; + codemirror.setValue(value); + expect(scope.foo).toBe(value); + }); + }); + }); + + describe('when the model changes', function () { + it('should update the IDE', function () { + var element = $compile('')(scope); + scope.foo = 'bar'; + scope.$apply(); + $timeout.flush(); + expect($.trim(element.siblings().text())).toBe(scope.foo); + }); }); - describe('compiling this directive', function () { - it('should throw an error if used against a non-textarea', function () { - function compile() { - $compile('
')($rootScope); - } - - expect(compile).toThrow(); - }); - - it('should not throw an error when used against a textarea', function () { - function compile() { - $compile('')($rootScope); - } - - expect(compile).not.toThrow(); - }); - - it('should throw an error when no ngModel attribute defined', function () { - function compile() { - $compile('')($rootScope); - } - - expect(compile).toThrow(); - }); - - it('should watch the uiCodemirror attribute', function () { - spyOn($rootScope, '$watch'); - $compile('')($rootScope); - $timeout.flush(); - expect($rootScope.$watch).toHaveBeenCalled(); - }); - - // Sorry I'm not enough familiar Jasmine to fix this... - /* - it('should include the passed options', function () { - spyOn(CodeMirror, 'fromTextArea'); - $compile('')($rootScope); - expect(CodeMirror.fromTextArea.mostRecentCall.args[1].foo).toEqual('bar'); - }); - - it('should include the default options', function () { - spyOn(CodeMirror, 'fromTextArea'); - $compile('')($rootScope); - expect(CodeMirror.fromTextArea.mostRecentCall.args[1].bar).toEqual('baz'); - }); - */ - }); - - describe('when the model changes', function () { - it('should update the IDE', function () { - var element = $compile('')($rootScope); - $rootScope.foo = 'bar'; - $rootScope.$apply(); - $timeout.flush(); - expect(element[0].nextSibling.CodeMirror.getValue()).toBe($rootScope.foo); - }); - }); - - describe('when the IDE changes', function () { - it('should update the model', function () { - var codemirror, - fromTextArea = CodeMirror.fromTextArea; - spyOn(CodeMirror, 'fromTextArea').andCallFake(function () { - codemirror = fromTextArea.apply(this, arguments); - return codemirror; - }); - $compile('')($rootScope); - $rootScope.foo = 'bar'; - $rootScope.$apply(); - $timeout.flush(); - var value = 'baz'; - codemirror.setValue(value); - expect($rootScope.foo).toBe(value); - }); - }); - - describe('when the model is undefined/null', function () { - it('should update the IDE with an empty string', function () { - var element = $compile('')($rootScope); - $rootScope.$apply(); - expect($rootScope.foo).toBe(undefined); - expect($.trim(element.siblings().text())).toBe(''); - $rootScope.foo = null; - $rootScope.$apply(); - expect($rootScope.foo).toBe(null); - expect($.trim(element.siblings().text())).toBe(''); - }); - }); - - describe('when the model is an object or an array', function () { - it('should throw an error', function () { - function compileWithObject() { - $compile('')($rootScope); - $timeout.flush(); - $rootScope.foo = {}; - $rootScope.$apply(); - } - - function compileWithArray() { - $compile('')($rootScope); - $timeout.flush(); - $rootScope.foo = []; - $rootScope.$apply(); - } - - expect(compileWithObject).toThrow(); - expect(compileWithArray).toThrow(); - }); - }); + + describe('when the model is undefined/null', function () { + it('should update the IDE with an empty string', function () { + var element = $compile('')(scope); + scope.$apply(); + expect(scope.foo).toBe(undefined); + expect($.trim(element.siblings().text())).toBe(''); + scope.foo = null; + scope.$apply(); + expect(scope.foo).toBe(null); + expect($.trim(element.siblings().text())).toBe(''); + }); + }); + + describe('when the model is an object or an array', function () { + it('should throw an error', function () { + function compileWithObject() { + $compile('')(scope); + $timeout.flush(); + scope.foo = {}; + scope.$apply(); + } + + function compileWithArray() { + $compile('')(scope); + $timeout.flush(); + scope.foo = []; + scope.$apply(); + } + + expect(compileWithObject).toThrow(); + expect(compileWithArray).toThrow(); + }); + }); }); \ No newline at end of file diff --git a/package.json b/package.json index 3ac30a9..d5ace26 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,6 @@ "devDependencies": { "grunt-recess": "~0.1.3", "async": "0.1.x", - "testacular": "~0.4.0" + "testacular": "~0.4.x" } } diff --git a/test/lib/codemirror/codemirror.js b/test/lib/codemirror/codemirror.js index 2003aa7..8fa0e94 100644 --- a/test/lib/codemirror/codemirror.js +++ b/test/lib/codemirror/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 3.0 +// CodeMirror version 3.02 // // CodeMirror is the only global var we claim window.CodeMirror = (function() { @@ -10,8 +10,8 @@ window.CodeMirror = (function() { // bugs and behavior differences. var gecko = /gecko\/\d/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 ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); + var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); var webkit = /WebKit\//.test(navigator.userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); var chrome = /Chrome\//.test(navigator.userAgent); @@ -24,8 +24,14 @@ window.CodeMirror = (function() { var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent); + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); + var windows = /windows/i.test(navigator.platform); + + var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (opera_version) opera_version = Number(opera_version[1]); + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); // Optimize some code when these features are not used var sawReadOnlySpans = false, sawCollapsedSpans = false; @@ -80,7 +86,9 @@ window.CodeMirror = (function() { function makeDisplay(place) { var d = {}; var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); - input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); + if (webkit) input.style.width = "1000px"; + else input.setAttribute("wrap", "off"); + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); // Wraps and hides input textarea d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); // The actual fake scrollbars. @@ -91,9 +99,9 @@ window.CodeMirror = (function() { d.lineDiv = elt("div"); d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); // Blinky cursor, and element used to ensure cursor fits at the end of a line - d.cursor = elt("pre", "\u00a0", "CodeMirror-cursor"); + d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); // Secondary cursor, shown when on a 'jump' in bi-directional text - d.otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); + d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); // Used to measure text size d.measure = elt("div", null, "CodeMirror-measure"); // Wraps everything that needs to exist inside the vertically-padded coordinate system @@ -160,6 +168,9 @@ window.CodeMirror = (function() { // detected d.pasteIncoming = false; + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + return d; } @@ -183,7 +194,9 @@ window.CodeMirror = (function() { suppressEdits: false, goalColumn: null, cantEdit: false, - keyMaps: [] + keyMaps: [], + overlays: [], + modeGen: 0 }; } @@ -194,9 +207,14 @@ window.CodeMirror = (function() { function loadMode(cm) { var doc = cm.view.doc; cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode); - doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); + doc.iter(0, doc.size, function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); cm.view.frontier = 0; startWorker(cm, 100); + cm.view.modeGen++; + if (cm.curOp) regChange(cm, 0, doc.size); } function wrappingChanged(cm) { @@ -342,13 +360,14 @@ window.CodeMirror = (function() { function alignHorizontally(cm) { var display = cm.display; - if (!display.alignWidgets && !display.gutters.firstChild) return; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft; var gutterW = display.gutters.offsetWidth, l = comp + "px"; for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; } - display.gutters.style.left = (comp + gutterW) + "px"; + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; } function maybeUpdateLineNumberWidth(cm) { @@ -412,7 +431,8 @@ window.CodeMirror = (function() { if (changes && maybeUpdateLineNumberWidth(cm)) changes = true; - display.sizer.style.marginLeft = display.scrollbarH.style.left = display.gutters.offsetWidth + "px"; + var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; + display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; // When merged lines are present, the line that needs to be // redrawn might not be the one that was changed. @@ -458,9 +478,11 @@ window.CodeMirror = (function() { return; intact.sort(function(a, b) {return a.from - b.from;}); + var focused = document.activeElement; if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; patchDisplay(cm, from, to, intact, positionsChangedFrom); display.lineDiv.style.display = ""; + if (document.activeElement != focused && focused.offsetHeight) focused.focus(); var different = from != display.showingFrom || to != display.showingTo || display.lastSizeC != display.wrapper.clientHeight; @@ -482,12 +504,19 @@ window.CodeMirror = (function() { } var diff = node.lineObj.height - height; if (height < 2) height = textHeight(display); - if (diff > .001 || diff < -.001) + if (diff > .001 || diff < -.001) { updateLineHeight(node.lineObj, height); + var widgets = node.lineObj.widgets; + if (widgets) for (var i = 0; i < widgets.length; ++i) + widgets[i].height = widgets[i].node.offsetHeight; + } } display.viewOffset = heightAtLine(cm, getLine(doc, from)); // Position the mover div to align with the current virtual scroll position display.mover.style.top = display.viewOffset + "px"; + + if (visibleLines(display, doc, viewPort).to >= to) + updateDisplayInner(cm, [], viewPort); return true; } @@ -528,9 +557,7 @@ window.CodeMirror = (function() { function patchDisplay(cm, from, to, intact, updateNumbersFrom) { var dims = getDimensions(cm); var display = cm.display, lineNumbers = cm.options.lineNumbers; - // 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 (!intact.length && !ie && (!webkit || !cm.display.currentWheelTarget)) + if (!intact.length && (!webkit || !cm.display.currentWheelTarget)) removeChildren(display.lineDiv); var container = display.lineDiv, cur = container.firstChild; @@ -540,7 +567,7 @@ window.CodeMirror = (function() { node.style.display = "none"; node.lineObj = null; } else { - container.removeChild(node); + node.parentNode.removeChild(node); } return next; } @@ -550,6 +577,17 @@ window.CodeMirror = (function() { if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift(); if (lineIsHidden(line)) { if (line.height != 0) updateLineHeight(line, 0); + if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) + if (line.widgets[i].showIfHidden) { + var prev = cur.previousSibling; + if (prev.nodeType == "pre") { + var wrap = elt("div", null, null, "position: relative"); + prev.parentNode.replaceChild(wrap, prev); + wrap.appendChild(prev); + prev = wrap; + } + prev.appendChild(buildLineWidget(line.widgets[i], prev, dims)); + } } else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) { // This line is intact. Skip to the actual node. Update its // line number if needed. @@ -582,8 +620,8 @@ window.CodeMirror = (function() { var wrap = elt("div", null, line.wrapClass, "position: relative"); if (cm.options.lineNumbers || markers) { var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " + - dims.fixedPos + "px")); - wrap.alignable = [gutterWrap]; + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); + if (cm.options.fixedGutter) wrap.alignable = [gutterWrap]; if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) wrap.lineNumber = gutterWrap.appendChild( elt("div", lineNumberFor(cm.options, lineNo), @@ -602,35 +640,38 @@ window.CodeMirror = (function() { if (line.bgClass) wrap.appendChild(elt("div", "\u00a0", line.bgClass + " 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.height != 0 ? gutterWrap : lineElement); - else - wrap.appendChild(node); - } - + if (line.widgets) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = buildLineWidget(widget, wrap, dims); + if (widget.above) + wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); + else + wrap.appendChild(node); + } if (ie_lt8) wrap.style.zIndex = 2; return wrap; } + function buildLineWidget(widget, wrap, dims) { + var 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"; + } + return node; + } + // SELECTION / CURSOR function updateSelection(cm) { @@ -765,7 +806,7 @@ window.CodeMirror = (function() { // HIGHLIGHT WORKER function startWorker(cm, time) { - if (cm.view.frontier < cm.display.showingTo) + if (cm.view.mode.startState && cm.view.frontier < cm.display.showingTo) cm.view.highlight.set(time, bind(highlightWorker, cm)); } @@ -777,7 +818,12 @@ window.CodeMirror = (function() { 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) { + var oldStyles = line.styles; + line.styles = highlightLine(cm, line, state); + var ischange = !oldStyles || oldStyles.length != line.styles.length; + for (var i = 0; !ischange && i < oldStyles.length; ++i) + ischange = oldStyles[i] != line.styles[i]; + if (ischange) { if (prevChange && prevChange.end == view.frontier) prevChange.end++; else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1}); } @@ -821,6 +867,7 @@ window.CodeMirror = (function() { function getStateBefore(cm, n) { var view = cm.view; + if (!view.mode.startState) return true; 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); @@ -842,7 +889,9 @@ window.CodeMirror = (function() { } function measureChar(cm, line, ch, data) { - var data = data || measureLine(cm, line), dir = -1; + var dir = -1; + data = data || measureLine(cm, line); + for (var pos = ch;; pos += dir) { var r = data[pos]; if (r) break; @@ -938,7 +987,7 @@ window.CodeMirror = (function() { // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" function intoCoordSystem(cm, lineObj, 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; + var size = widgetHeight(lineObj.widgets[i]); rect.top += size; rect.bottom += size; } if (context == "line") return rect; @@ -1010,8 +1059,9 @@ window.CodeMirror = (function() { var lineObj = getLine(doc, lineNo); var found = coordsCharInner(cm, lineObj, lineNo, x, y); var merged = collapsedSpanAtEnd(lineObj); - if (merged && found.ch == lineRight(lineObj)) - lineNo = merged.find().to.line; + var mergedPos = merged && merged.find(); + if (merged && found.ch >= mergedPos.from.ch) + lineNo = mergedPos.to.line; else return found; } @@ -1121,6 +1171,9 @@ window.CodeMirror = (function() { var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right; display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px"; view.maxLineChanged = false; + var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); + if (maxScrollLeft < view.scrollLeft) + setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); } var newScrollPos, updated; if (op.selectionChanged) { @@ -1237,7 +1290,7 @@ window.CodeMirror = (function() { on(d.scroller, "mousedown", operation(cm, onMouseDown)); on(d.scroller, "dblclick", operation(cm, e_preventDefault)); on(d.lineSpace, "selectstart", function(e) { - if (!mouseEventInWidget(d, e)) e_preventDefault(e); + if (!eventInWidget(d, e)) e_preventDefault(e); }); // Gecko browsers fire contextmenu *after* opening the menu, at // which point we can't mess with it anymore. Context menu is @@ -1264,13 +1317,24 @@ window.CodeMirror = (function() { on(d.scrollbarV, "mousedown", reFocus); // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - on(window, "resize", function resizeHandler() { + + if (!window.registered) window.registered = 0; + ++window.registered; + function onResize() { // Might be a text scaling operation, clear size caches. d.cachedCharWidth = d.cachedTextHeight = null; clearCaches(cm); - if (d.wrapper.parentNode) updateDisplay(cm, true); - else off(window, "resize", resizeHandler); - }); + updateDisplay(cm, true); + } + on(window, "resize", onResize); + // Above handler holds on to the editor and its data structures. + // Here we poll to unregister it when the editor is no longer in + // the document, so that it can be garbage-collected. + setTimeout(function unregister() { + for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} + if (p) setTimeout(unregister, 5000); + else {--window.registered; off(window, "resize", onResize);} + }, 5000); on(d.input, "keyup", operation(cm, function(e) { if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; @@ -1292,7 +1356,11 @@ window.CodeMirror = (function() { on(d.scroller, "dragover", drag_); on(d.scroller, "drop", operation(cm, onDrop)); } - on(d.scroller, "paste", function(){focusInput(cm); fastPoll(cm);}); + on(d.scroller, "paste", function(e){ + if (eventInWidget(d, e)) return; + focusInput(cm); + fastPoll(cm); + }); on(d.input, "paste", function() { d.pasteIncoming = true; fastPoll(cm); @@ -1316,10 +1384,12 @@ window.CodeMirror = (function() { }); } - function mouseEventInWidget(display, e) { - for (var n = e_target(e); n != display.wrapper; n = n.parentNode) + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n) return true; if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || n.parentNode == display.sizer && n != display.mover) return true; + } } function posFromMouse(cm, e, liberal) { @@ -1341,7 +1411,7 @@ window.CodeMirror = (function() { var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc; sel.shift = e_prop(e, "shiftKey"); - if (mouseEventInWidget(display, e)) { + if (eventInWidget(display, e)) { if (!webkit) { display.scroller.draggable = false; setTimeout(function(){display.scroller.draggable = true;}, 100); @@ -1477,7 +1547,8 @@ window.CodeMirror = (function() { function onDrop(e) { var cm = this; - if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; + if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) + return; e_preventDefault(e); var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; if (!pos || isReadOnly(cm)) return; @@ -1502,7 +1573,8 @@ window.CodeMirror = (function() { // 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))) { cm.view.draggingText(e); - if (ie) setTimeout(bind(focusInput, cm), 50); + // Ensure the editor is re-focused + setTimeout(bind(focusInput, cm), 20); return; } try { @@ -1546,13 +1618,24 @@ window.CodeMirror = (function() { } function onDragStart(cm, e) { + if (eventInWidget(cm.display, e)) return; + var txt = cm.getSelection(); e.dataTransfer.setData("Text", txt); // Use dummy image instead of default browsers image. // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) - e.dataTransfer.setDragImage(elt('img'), 0, 0); + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + if (opera) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (opera) img.parentNode.removeChild(img); + } } function setScrollTop(cm, val) { @@ -1565,6 +1648,7 @@ window.CodeMirror = (function() { } function setScrollLeft(cm, val, isScroller) { if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); cm.view.scrollLeft = val; alignHorizontally(cm); if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; @@ -1582,7 +1666,7 @@ window.CodeMirror = (function() { // is that it gives us a chance to update the display before the // actual scrolling happens, reducing flickering. - var wheelSamples = 0, wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit = null; + var wheelSamples = 0, wheelPixelsPerUnit = null; // Fill in a browser-detected starting value on browsers where we // know one. These don't have to be accurate -- the result of them // being wrong would just be a slight flicker on the first wheel @@ -1611,7 +1695,7 @@ window.CodeMirror = (function() { } } - var scroll = cm.display.scroller; + var display = cm.display, scroll = display.scroller; // On some browsers, horizontal scrolling will cause redraws to // happen before the gutter has been realigned, causing it to // wriggle around in a most unseemly way. When we have an @@ -1623,35 +1707,35 @@ window.CodeMirror = (function() { setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); e_preventDefault(e); - wheelStartX = null; // Abort measurement, if in progress + display.wheelStartX = null; // Abort measurement, if in progress return; } if (dy && wheelPixelsPerUnit != null) { var pixels = dy * wheelPixelsPerUnit; - var top = cm.view.scrollTop, bot = top + cm.display.wrapper.clientHeight; + var top = cm.view.scrollTop, bot = top + display.wrapper.clientHeight; if (pixels < 0) top = Math.max(0, top + pixels - 50); else bot = Math.min(cm.view.doc.height, bot + pixels + 50); updateDisplay(cm, [], {top: top, bottom: bot}); } if (wheelSamples < 20) { - if (wheelStartX == null) { - wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop; - wheelDX = dx; wheelDY = dy; + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; setTimeout(function() { - if (wheelStartX == null) return; - var movedX = scroll.scrollLeft - wheelStartX; - var movedY = scroll.scrollTop - wheelStartY; - var sample = (movedY && wheelDY && movedY / wheelDY) || - (movedX && wheelDX && movedX / wheelDX); - wheelStartX = wheelStartY = null; + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; if (!sample) return; wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); ++wheelSamples; }, 200); } else { - wheelDX += dx; wheelDY += dy; + display.wheelDX += dx; display.wheelDY += dy; } } } @@ -1697,7 +1781,6 @@ window.CodeMirror = (function() { }, 50); var name = keyNames[e_prop(e, "keyCode")], handled = false; - var flipCtrlCmd = mac && (opera || qtwebkit); if (name == null || e.altGraphKey) return false; if (e_prop(e, "altKey")) name = "Alt-" + name; if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; @@ -1794,7 +1877,10 @@ window.CodeMirror = (function() { var detectingSelectAll; function onContextMenu(cm, e) { - var display = cm.display, sel = cm.view.sel; + var display = cm.display; + if (eventInWidget(display, e)) return; + + var sel = cm.view.sel; var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; if (!pos || opera) return; // Opera is difficult. if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) @@ -1904,7 +1990,7 @@ window.CodeMirror = (function() { if (!cm.options.lineWrapping) { checkWidthStart = lineNo(visualLine(doc, firstLine)); doc.iter(checkWidthStart, to.line + 1, function(line) { - if (lineLength(doc, line) == view.maxLineLength) { + if (line == view.maxLine) { recomputeMaxLength = true; return true; } @@ -2351,6 +2437,25 @@ window.CodeMirror = (function() { } }, + addOverlay: operation(null, function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.view.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.view.modeGen++; + regChange(this, 0, this.view.doc.size); + }), + removeOverlay: operation(null, function(spec) { + var overlays = this.view.overlays; + for (var i = 0; i < overlays.length; ++i) { + if (overlays[i].modeSpec == spec) { + overlays.splice(i, 1); + this.view.modeGen++; + regChange(this, 0, this.view.doc.size); + return; + } + } + }), + undo: operation(null, function() {unredoHelper(this, "undo");}), redo: operation(null, function() {unredoHelper(this, "redo");}), @@ -2522,23 +2627,10 @@ window.CodeMirror = (function() { }), addLineWidget: operation(null, function(handle, node, options) { - var widget = options || {}; - widget.node = node; - if (widget.noHScroll) this.display.alignWidgets = true; - changeLine(this, handle, function(line) { - (line.widgets || (line.widgets = [])).push(widget); - widget.line = line; - return true; - }); - return widget; + return addLineWidget(this, handle, node, options); }), - removeLineWidget: operation(null, function(widget) { - var ws = widget.line.widgets, no = lineNo(widget.line); - if (no == null) return; - for (var i = 0; i < ws.length; ++i) if (ws[i] == widget) ws.splice(i--, 1); - regChange(this, no, no + 1); - }), + removeLineWidget: function(widget) { widget.clear(); }, lineInfo: function(line) { if (typeof line == "number") { @@ -2663,7 +2755,8 @@ window.CodeMirror = (function() { // Stuff used by commands, probably not much use to outside code. moveH: operation(null, function(dir, unit) { var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to; - if (sel.shift || sel.extend || posEq(sel.from, sel.to)) pos = findPosH(this, dir, unit, true); + if (sel.shift || sel.extend || posEq(sel.from, sel.to)) + pos = findPosH(this, dir, unit, this.options.rtlMoveVisually); extendSelection(this, pos, pos, dir); }), @@ -2713,7 +2806,7 @@ window.CodeMirror = (function() { return clipPos(doc, {line: lineNo, ch: ch}); }, indexFromPos: function (coords) { - if (coords.line < 0 || coords.ch < 0) return 0; + coords = clipPos(this.view.doc, coords); var index = coords.ch; this.view.doc.iter(0, coords.line, function (line) { index += line.text.length + 1; @@ -2735,8 +2828,12 @@ window.CodeMirror = (function() { scrollIntoView: function(pos) { if (typeof pos == "number") pos = {line: pos, ch: 0}; - pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head; - scrollPosIntoView(this, pos); + if (!pos || pos.line != null) { + pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head; + scrollPosIntoView(this, pos); + } else { + scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom); + } }, setSize: function(width, height) { @@ -2755,8 +2852,11 @@ window.CodeMirror = (function() { refresh: function() { clearCaches(this); - if (this.display.scroller.scrollHeight > this.view.scrollTop) - this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = this.view.scrollTop; + var sTop = this.view.scrollTop, sLeft = this.view.scrollLeft; + if (this.display.scroller.scrollHeight > sTop) + this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = sTop; + if (this.display.scroller.scrollWidth > sLeft) + this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = sLeft; updateDisplay(this, true); }, @@ -2795,6 +2895,7 @@ window.CodeMirror = (function() { updateDisplay(cm, true); }, true); option("electricChars", true); + option("rtlMoveVisually", !windows); option("theme", "default", function(cm) { themeChanged(cm); @@ -2811,6 +2912,10 @@ window.CodeMirror = (function() { setGuttersForLineNumbers(cm.options); guttersChanged(cm); }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); option("lineNumbers", false, function(cm) { setGuttersForLineNumbers(cm.options); guttersChanged(cm); @@ -2867,7 +2972,7 @@ window.CodeMirror = (function() { }; CodeMirror.getMode = function(options, spec) { - var spec = CodeMirror.resolveMode(spec); + spec = CodeMirror.resolveMode(spec); var mfactory = modes[spec.name]; if (!mfactory) return CodeMirror.getMode(options, "text/plain"); var modeObj = mfactory(options, spec); @@ -3204,11 +3309,12 @@ window.CodeMirror = (function() { this.type = type; this.cm = cm; } + CodeMirror.TextMarker = TextMarker; TextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; startOperation(this.cm); - var min = null, max = null; + var view = this.cm.view, min = null, max = null; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; var span = getMarkedSpanFor(line.markedSpans, this); @@ -3219,6 +3325,15 @@ window.CodeMirror = (function() { else if (this.collapsed && !lineIsHidden(line)) updateLineHeight(line, textHeight(this.cm.display)); } + if (this.collapsed && !this.cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(view.doc, this.lines[i]), len = lineLength(view.doc, visual); + if (len > view.maxLineLength) { + view.maxLine = visual; + view.maxLineLength = len; + view.maxLineChanged = true; + } + } + if (min != null) regChange(this.cm, min, max + 1); this.lines.length = 0; this.explicitlyCleared = true; @@ -3245,6 +3360,18 @@ window.CodeMirror = (function() { return from && {from: from, to: to}; }; + TextMarker.prototype.getOptions = function(copyWidget) { + var repl = this.replacedWith; + return {className: this.className, + inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight, + atomic: this.atomic, + collapsed: this.collapsed, + clearOnEnter: this.clearOnEnter, + replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl, + readOnly: this.readOnly, + startStyle: this.startStyle, endStyle: this.endStyle}; + }; + function markText(cm, from, to, options, type) { var doc = cm.view.doc; var marker = new TextMarker(cm, type); @@ -3259,6 +3386,8 @@ window.CodeMirror = (function() { var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd; doc.iter(curLine, to.line + 1, function(line) { + if (marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.view.maxLine) + cm.curOp.updateMaxLine = true; var span = {from: null, to: null, marker: marker}; size += line.text.length; if (curLine == from.line) {span.from = from.ch; size -= from.ch;} @@ -3269,10 +3398,11 @@ window.CodeMirror = (function() { else updateLineHeight(line, 0); } addMarkedSpan(line, span); - if (marker.collapsed && curLine == from.line && lineIsHidden(line)) - updateLineHeight(line, 0); ++curLine; }); + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(line)) updateLineHeight(line, 0); + }); if (marker.readOnly) { sawReadOnlySpans = true; @@ -3447,9 +3577,12 @@ window.CodeMirror = (function() { return true; } } - window.lineIsHidden = lineIsHidden; function lineIsHiddenInner(line, span) { - if (span.to == null || span.marker.inclusiveRight && span.to == line.text.length) + if (span.to == null) { + var end = span.marker.find().to, endLine = getLine(lineDoc(line), end.line); + return lineIsHiddenInner(endLine, getMarkedSpanFor(endLine.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) return true; for (var sp, i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i]; @@ -3491,6 +3624,63 @@ window.CodeMirror = (function() { line.markedSpans = spans; } + // LINE WIDGETS + + var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { + for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.cm = cm; + this.node = node; + }; + function widgetOperation(f) { + return function() { + startOperation(this.cm); + try {var result = f.apply(this, arguments);} + finally {endOperation(this.cm);} + return result; + }; + } + LineWidget.prototype.clear = widgetOperation(function() { + var ws = this.line.widgets, no = lineNo(this.line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); + regChange(this.cm, no, no + 1); + }); + LineWidget.prototype.changed = widgetOperation(function() { + var oldH = this.height; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(this.line, this.line.height + diff); + var no = lineNo(this.line); + regChange(this.cm, no, no + 1); + }); + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1) + removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(cm, handle, node, options) { + var widget = new LineWidget(cm, node, options); + if (widget.noHScroll) cm.display.alignWidgets = true; + changeLine(cm, handle, function(line) { + (line.widgets || (line.widgets = [])).push(widget); + widget.line = line; + if (!lineIsHidden(line) || widget.showIfHidden) { + var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) + setTimeout(function() {cm.display.scroller.scrollTop += widget.height;}); + } + return true; + }); + return widget; + } + // LINE DATA STRUCTURE // Line objects. These hold state related to a line, including @@ -3504,7 +3694,8 @@ window.CodeMirror = (function() { function updateLine(cm, line, text, markedSpans) { line.text = text; - line.stateAfter = line.styles = null; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; if (line.order != null) line.order = null; detachMarkedSpans(line); attachMarkedSpans(line, markedSpans); @@ -3521,31 +3712,72 @@ window.CodeMirror = (function() { // 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); + function runMode(cm, text, mode, state, f) { + var flattenSpans = cm.options.flattenSpans; + var curText = "", curStyle = null; + var stream = new StringStream(text, cm.options.tabSize); + if (text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { - var style = mode.token(stream, state), substr = stream.current(); + var style = mode.token(stream, state); + if (stream.pos > 5000) { + flattenSpans = false; + // Webkit seems to refuse to render text nodes longer than 57444 characters + stream.pos = Math.min(text.length, stream.start + 50000); + style = null; + } + var 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; - } + if (curText) f(curText, curStyle); 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 (curText) f(curText, curStyle); + } + + function highlightLine(cm, line, state) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.view.modeGen]; + // Compute the base array of styles + runMode(cm, line.text, cm.view.mode, state, function(txt, style) {st.push(txt, style);}); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.view.overlays.length; ++o) { + var overlay = cm.view.overlays[o], i = 1; + runMode(cm, line.text, overlay.mode, true, function(txt, style) { + var start = i, len = txt.length; + // Ensure there's a token end at the current position, and that i points at it + while (len) { + var cur = st[i], len_ = cur.length; + if (len_ <= len) { + len -= len_; + } else { + st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len)); + len = 0; + } + i += 2; + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, txt, style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = cur ? cur + " " + style : style; + } + } + }); } - 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; + + return st; + } + + function getLineStyles(cm, line) { + if (!line.styles || line.styles[0] != cm.view.modeGen) + line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + return line.styles; } // Lightweight form of highlight -- proceed over this line and @@ -3580,8 +3812,6 @@ window.CodeMirror = (function() { if (line.textClass) builder.pre.className = line.textClass; do { - if (!line.styles) - highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); builder.measure = line == realLine && measure; builder.pos = 0; builder.addToken = builder.measure ? buildTokenMeasure : buildToken; @@ -3589,7 +3819,7 @@ window.CodeMirror = (function() { measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); builder.addedOne = true; } - var next = insertLineContent(line, builder); + var next = insertLineContent(line, builder, getLineStyles(cm, line)); sawBefore = line == lineBefore; if (next) { line = getLine(cm.view.doc, next.to.line); @@ -3646,7 +3876,7 @@ window.CodeMirror = (function() { function buildTokenMeasure(builder, text, style, startStyle, endStyle) { for (var i = 0; i < text.length; ++i) { - if (i && i < text.length - 1 && + if (i && i < text.length && builder.cm.options.lineWrapping && spanAffectsWrapping.test(text.slice(i - 1, i + 1))) builder.pre.appendChild(elt("wbr")); @@ -3671,16 +3901,16 @@ window.CodeMirror = (function() { // Outputs a number of spans to make up a line, taking highlighting // and marked text into account. - function insertLineContent(line, builder) { - var st = line.styles, spans = line.markedSpans; + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans; if (!spans) { - for (var i = 0; i < st.length; i+=2) - builder.addToken(builder, st[i], styleToClass(st[i+1])); + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, styles[i], styleToClass(styles[i+1])); return; } var allText = line.text, len = allText.length; - var pos = 0, i = 0, text = "", style; + var pos = 0, i = 1, text = "", style; var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; for (;;) { if (nextChange == pos) { // Update current marker set @@ -3724,7 +3954,7 @@ window.CodeMirror = (function() { pos = end; spanStartStyle = ""; } - text = st[i++]; style = styleToClass(st[i++]); + text = styles[i++]; style = styleToClass(styles[i++]); } } } @@ -3896,6 +4126,11 @@ window.CodeMirror = (function() { return no; } + function lineDoc(line) { + for (var d = line.parent; d.parent; d = d.parent) {} + return d; + } + function lineAtHeight(chunk, h) { var n = 0; outer: do { @@ -4177,7 +4412,9 @@ window.CodeMirror = (function() { } function removeChildren(e) { - e.innerHTML = ""; + // IE will break all parent-child relations in subnodes when setting innerHTML + if (!ie) e.innerHTML = ""; + else while (e.firstChild) e.removeChild(e.firstChild); return e; } @@ -4300,7 +4537,7 @@ window.CodeMirror = (function() { if (!order) return f(from, to, "ltr"); for (var i = 0; i < order.length; ++i) { var part = order[i]; - if (part.from < to && part.to > from) + if (part.from < to && part.to > from || from == to && part.to == from) f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); } } @@ -4418,24 +4655,20 @@ window.CodeMirror = (function() { var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; return function charOrdering(str) { if (!bidiRE.test(str)) return false; - var len = str.length, types = [], startType = null; - for (var i = 0, type; i < len; ++i) { + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) types.push(type = charType(str.charCodeAt(i))); - if (startType == null) { - if (type == "L") startType = "L"; - else if (type == "R" || type == "r") startType = "R"; - } - } - if (startType == null) startType = "L"; // W1. Examine each non-spacing mark (NSM) in the level run, and // change the type of the NSM to the type of the previous // character. If the NSM is at the start of the level run, it will // get the type of sor. - for (var i = 0, prev = startType; i < len; ++i) { + for (var i = 0, prev = outerType; i < len; ++i) { var type = types[i]; if (type == "m") types[i] = prev; else prev = type; @@ -4446,7 +4679,7 @@ window.CodeMirror = (function() { // AL is found, change the type of the European number to Arabic // number. // W3. Change all ALs to R. - for (var i = 0, cur = startType; i < len; ++i) { + for (var i = 0, cur = outerType; i < len; ++i) { var type = types[i]; if (type == "1" && cur == "r") types[i] = "n"; else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } @@ -4481,7 +4714,7 @@ window.CodeMirror = (function() { // W7. Search backwards from each instance of a European number // until the first strong type (R, L, or sor) is found. If an L is // found, then change the type of the European number to L. - for (var i = 0, cur = startType; i < len; ++i) { + for (var i = 0, cur = outerType; i < len; ++i) { var type = types[i]; if (cur == "L" && type == "1") types[i] = "L"; else if (isStrong.test(type)) cur = type; @@ -4496,8 +4729,8 @@ window.CodeMirror = (function() { for (var i = 0; i < len; ++i) { if (isNeutral.test(types[i])) { for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} - var before = (i ? types[i-1] : startType) == "L"; - var after = (end < len - 1 ? types[end] : startType) == "L"; + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len - 1 ? types[end] : outerType) == "L"; var replace = before || after ? "L" : "R"; for (var j = i; j < end; ++j) types[j] = replace; i = end - 1; @@ -4547,7 +4780,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.0"; + CodeMirror.version = "3.02"; return CodeMirror; })();