Web Inspector: Should update to CodeMirror 4.1
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Apr 2014 00:06:46 +0000 (00:06 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Apr 2014 00:06:46 +0000 (00:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=132140

Patch by Jonathan Wells <jonowells@apple.com> on 2014-04-24
Reviewed by Timothy Hatcher.

Updated to CodeMirror 4.1, which contains several bugfixes.
Release notes: http://codemirror.net/doc/releases.html#v4

* Tools/PrettyPrinting/codemirror.js:
* Tools/PrettyPrinting/css.js:
* Tools/PrettyPrinting/javascript.js:
* UserInterface/External/CodeMirror/codemirror.js:
* UserInterface/External/CodeMirror/css.js:
* UserInterface/External/CodeMirror/javascript.js:
* UserInterface/External/CodeMirror/livescript.js:
* UserInterface/External/CodeMirror/runmode.js:
* UserInterface/External/CodeMirror/xml.js:
Updated to CodeMirror 4.1.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@167781 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Tools/PrettyPrinting/codemirror.js
Source/WebInspectorUI/Tools/PrettyPrinting/css.js
Source/WebInspectorUI/Tools/PrettyPrinting/javascript.js
Source/WebInspectorUI/UserInterface/External/CodeMirror/codemirror.js
Source/WebInspectorUI/UserInterface/External/CodeMirror/css.js
Source/WebInspectorUI/UserInterface/External/CodeMirror/javascript.js
Source/WebInspectorUI/UserInterface/External/CodeMirror/livescript.js
Source/WebInspectorUI/UserInterface/External/CodeMirror/runmode.js
Source/WebInspectorUI/UserInterface/External/CodeMirror/xml.js

index b10fb9c..97a0448 100644 (file)
@@ -1,5 +1,26 @@
 2014-04-24  Jonathan Wells  <jonowells@apple.com>
 
+        Web Inspector: Should update to CodeMirror 4.1
+        https://bugs.webkit.org/show_bug.cgi?id=132140
+
+        Reviewed by Timothy Hatcher.
+
+        Updated to CodeMirror 4.1, which contains several bugfixes.
+        Release notes: http://codemirror.net/doc/releases.html#v4
+
+        * Tools/PrettyPrinting/codemirror.js:
+        * Tools/PrettyPrinting/css.js:
+        * Tools/PrettyPrinting/javascript.js:
+        * UserInterface/External/CodeMirror/codemirror.js:
+        * UserInterface/External/CodeMirror/css.js:
+        * UserInterface/External/CodeMirror/javascript.js:
+        * UserInterface/External/CodeMirror/livescript.js:
+        * UserInterface/External/CodeMirror/runmode.js:
+        * UserInterface/External/CodeMirror/xml.js:
+        Updated to CodeMirror 4.1.
+
+2014-04-24  Jonathan Wells  <jonowells@apple.com>
+
         Web Inspector: CSS autofill suggests properties instead of values when values are needed
         https://bugs.webkit.org/show_bug.cgi?id=132090
 
@@ -10,7 +31,7 @@
         with CSS autofill, CSS code coloring, and also
         https://bugs.webkit.org/show_bug.cgi?id=131859. The main issue is that
         CodeMirror 4's CSS mode (css.js) stores its parsing modes in a different
-        data structure. All references to state.stack are obscelete. Many
+        data structure. All references to state.stack are obsolete. Many
         are fixed here and more will be in an upcoming patch.
 
         * Tools/PrettyPrinting/CodeMirrorFormatters.js: Removed exception for checking state.stack properties.
index 1a70cd6..0ab2177 100644 (file)
@@ -33,7 +33,6 @@
   var presto = /Opera\//.test(navigator.userAgent);
   var safari = /Apple Computer/.test(navigator.vendor);
   var khtml = /KHTML\//.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 phantom = /PhantomJS/.test(navigator.userAgent);
 
       d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
     } else d.gutterFiller.style.display = "";
 
-    if (mac_geLion && scrollbarWidth(d.measure) === 0) {
-      d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
-      var barMouseDown = function(e) {
-        if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
-          operation(cm, onMouseDown)(e);
-      };
-      on(d.scrollbarV, "mousedown", barMouseDown);
-      on(d.scrollbarH, "mousedown", barMouseDown);
+    if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
+      if (scrollbarWidth(d.measure) === 0) {
+        var w = mac && !mac_geMountainLion ? "12px" : "18px";
+        d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
+        var barMouseDown = function(e) {
+          if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
+            operation(cm, onMouseDown)(e);
+        };
+        on(d.scrollbarV, "mousedown", barMouseDown);
+        on(d.scrollbarH, "mousedown", barMouseDown);
+      }
+      cm.state.checkedOverlayScrollbar = true;
     }
   }
 
 
   // Set a new selection.
   function setSelection(doc, sel, options) {
-    if (options && options.origin && doc.cm) doc.cm.curOp.origin = options.origin;
     setSelectionNoUndo(doc, sel, options);
     addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
   }
 
     doc.sel = sel;
 
-    if (doc.cm)
-      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
-        doc.cm.curOp.cursorActivity = true;
+    if (doc.cm) {
+      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
+      signalCursorActivity(doc.cm);
+    }
     signalLater(doc, "cursorActivity", doc);
   }
 
     if (display.cachedPaddingH) return display.cachedPaddingH;
     var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
     var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
-    return display.cachedPaddingH = {left: parseInt(style.paddingLeft),
-                                     right: parseInt(style.paddingRight)};
+    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
+    if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
+    return data;
   }
 
   // Ensure the lineView.wrapping.heights array is populated. This is
       updateInput: null,       // Whether to reset the input textarea
       typing: false,           // Whether this reset should be careful to leave existing text (for compositing)
       changeObjs: null,        // Accumulated changes, for firing change events
-      origin: null,            // Selection's origin
-      cursorActivity: false,   // Whether to fire a cursorActivity event
+      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
       selectionChanged: false, // Whether the selection needs to be redrawn
       updateMaxLine: false,    // Set when the widest line needs to be determined anew
       scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
       delayedCallbacks = null;
     }
     // Fire change events, and delayed event handlers
-    if (op.changeObjs) {
-      for (var i = 0; i < op.changeObjs.length; i++)
-        signal(cm, "change", cm, op.changeObjs[i]);
+    if (op.changeObjs)
       signal(cm, "changes", cm, op.changeObjs);
-    }
-    if (op.cursorActivity) signal(cm, "cursorActivity", cm, op.origin);
     if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
+    if (op.cursorActivityHandlers)
+      for (var i = 0; i < op.cursorActivityHandlers.length; i++)
+        op.cursorActivityHandlers[i](cm);
   }
 
   // Run the given function in an operation
     // possible when it is clear that nothing happened. hasSelection
     // will be the case when there is a lot of text in the textarea,
     // in which case reading its value would be expensive.
-    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false;
+    if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput)
+      return false;
+    // See paste handler for more on the fakedLastChar kludge
+    if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
+      input.value = input.value.substring(0, input.value.length - 1);
+      cm.state.fakedLastChar = false;
+    }
     var text = input.value;
     // If nothing changed, bail.
     if (text == prevInput && !cm.somethingSelected()) return false;
       fastPoll(cm);
     });
     on(d.input, "paste", function() {
+      // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
+      // Add a char to the end of textarea before paste occur so that
+      // selection doesn't span to the end of textarea.
+      if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
+        var start = d.input.selectionStart, end = d.input.selectionEnd;
+        d.input.value += "$";
+        d.input.selectionStart = start;
+        d.input.selectionEnd = end;
+        cm.state.fakedLastChar = true;
+      }
       cm.state.pasteIncoming = true;
       fastPoll(cm);
     });
     var coords = coordsChar(cm, x, y), line;
     if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
       var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
-      coords = Pos(coords.line, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff);
+      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
     }
     return coords;
   }
     e_preventDefault(e);
 
     var ourRange, ourIndex, startSel = doc.sel;
-    if (addNew) {
+    if (addNew && !e.shiftKey) {
       ourIndex = doc.sel.contains(start);
       if (ourIndex > -1)
         ourRange = doc.sel.ranges[ourIndex];
     if (!addNew) {
       ourIndex = 0;
       setSelection(doc, new Selection([ourRange], 0), sel_mouse);
+      startSel = doc.sel;
     } else if (ourIndex > -1) {
       replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
     } else {
     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
       return;
     e_preventDefault(e);
-    if (ie_upto10) lastDrop = +new Date;
+    if (ie) lastDrop = +new Date;
     var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
     if (!pos || isReadOnly(cm)) return;
     // Might be a file drop, in which case we simply extract the text
   }
 
   function onDragStart(cm, e) {
-    if (ie_upto10 && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
+    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
 
     e.dataTransfer.setData("Text", cm.getSelection());
       signal(cm, "focus", cm);
       cm.state.focused = true;
       addClass(cm.display.wrapper, "CodeMirror-focused");
-      if (!cm.curOp) {
+      // The prevInput test prevents this from firing when a context
+      // menu is closed (since the resetInput would kill the
+      // select-all detection hack)
+      if (!cm.curOp && cm.display.selForContextMenu == cm.doc.sel) {
         resetInput(cm);
         if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
       }
     resetInput(cm);
     // Adds "Select all" to context menu in FF
     if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
+    display.selForContextMenu = cm.doc.sel;
 
     // Select-all will be greyed out if there's nothing to select, so
     // this adds a zero-width space so that we can later check whether
     // it got selected.
     function prepareSelectAllHack() {
       if (display.input.selectionStart != null) {
-        var extval = display.input.value = "\u200b" + (cm.somethingSelected() ? display.input.value : "");
-        display.prevInput = "\u200b";
+        var selected = cm.somethingSelected();
+        var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
+        display.prevInput = selected ? "" : "\u200b";
         display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
       }
     }
       if (display.input.selectionStart != null) {
         if (!ie || ie_upto8) prepareSelectAllHack();
         clearTimeout(detectingSelectAll);
-        var i = 0, poll = function(){
-          if (display.prevInput == "\u200b" && display.input.selectionStart == 0)
+        var i = 0, poll = function() {
+          if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
             operation(cm, commands.selectAll)(cm);
           else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
           else resetInput(cm);
     }
 
     if (doc.sel.contains(change.from, change.to) > -1)
-      cm.curOp.cursorActivity = true;
+      signalCursorActivity(cm);
 
     updateDoc(doc, change, spans, estimateHeight(cm));
 
     else
       regChange(cm, from.line, to.line + 1, lendiff);
 
-    if (hasHandler(cm, "change") || hasHandler(cm, "changes"))
-      (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push({
+    var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
+    if (changeHandler || changesHandler) {
+      var obj = {
         from: from, to: to,
         text: change.text,
         removed: change.removed,
         origin: change.origin
-      });
+      };
+      if (changeHandler) signalLater(cm, "change", cm, obj);
+      if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj);
+    }
   }
 
   function replaceRange(doc, code, from, to, origin) {
       pos = clipPos(this.doc, pos);
       var styles = getLineStyles(this, getLine(this.doc, pos.line));
       var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
-      if (ch == 0) return styles[2];
-      for (;;) {
+      var type;
+      if (ch == 0) type = styles[2];
+      else for (;;) {
         var mid = (before + after) >> 1;
         if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
         else if (styles[mid * 2 + 1] < ch) before = mid + 1;
-        else return styles[mid * 2 + 2];
+        else { type = styles[mid * 2 + 2]; break; }
       }
+      var cut = type ? type.indexOf("cm-overlay ") : -1;
+      return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
     },
 
     getModeAt: function(pos) {
         }
         if (!style) return;
         if (overlay.opaque) {
-          st.splice(start, i - start, end, style);
+          st.splice(start, i - start, end, "cm-overlay " + style);
           i = start + 2;
         } else {
           for (; start < i; start += 2) {
             var cur = st[start+1];
-            st[start+1] = cur ? cur + " " + style : style;
+            st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
           }
         }
       }, lineClasses);
       }
       return parts;
     },
-    replaceSelection: docMethodOp(function(code, collapse, origin) {
+    replaceSelection: function(code, collapse, origin) {
       var dup = [];
       for (var i = 0; i < this.sel.ranges.length; i++)
         dup[i] = code;
       this.replaceSelections(dup, collapse, origin || "+input");
-    }),
-    replaceSelections: function(code, collapse, origin) {
+    },
+    replaceSelections: docMethodOp(function(code, collapse, origin) {
       var changes = [], sel = this.sel;
       for (var i = 0; i < sel.ranges.length; i++) {
         var range = sel.ranges[i];
         makeChange(this, changes[i]);
       if (newSel) setSelectionReplaceHistory(this, newSel);
       else if (this.cm) ensureCursorVisible(this.cm);
-    },
+    }),
     undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
     redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
     undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
     return e_defaultPrevented(e) || e.codemirrorIgnore;
   }
 
+  function signalCursorActivity(cm) {
+    var arr = cm._handlers && cm._handlers.cursorActivity;
+    if (!arr) return;
+    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
+    for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
+      set.push(arr[i]);
+  }
+
   function hasHandler(emitter, type) {
     var arr = emitter._handlers && emitter._handlers[type];
     return arr && arr.length > 0;
 
   // THE END
 
-  CodeMirror.version = "4.0.4";
+  CodeMirror.version = "4.1.0";
 
   return CodeMirror;
 });
index 3426732..a2b80df 100644 (file)
@@ -92,7 +92,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
 
   function tokenParenthesized(stream, state) {
     stream.next(); // Must be '('
-    if (!stream.match(/\s*[\"\']/, false))
+    if (!stream.match(/\s*[\"\')]/, false))
       state.tokenize = tokenString(")");
     else
       state.tokenize = null;
@@ -453,13 +453,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
     // SVG-specific
     "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
     "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
-    "color-interpolation", "color-interpolation-filters", "color-profile",
+    "color-interpolation", "color-interpolation-filters",
     "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
     "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
     "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
     "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
     "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
-    "glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode"
+    "glyph-orientation-vertical", "text-anchor", "writing-mode"
   ], propertyKeywords = keySet(propertyKeywords_);
 
   var nonStandardPropertyKeywords = [
index 9318aab..daf138b 100644 (file)
@@ -366,6 +366,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
     if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
     if (type == "{") return contCommasep(objprop, "}", null, maybeop);
+    if (type == "quasi") { return pass(quasi, maybeop); }
     return cont();
   }
   function maybeexpression(type) {
@@ -390,21 +391,22 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
       if (value == "?") return cont(expression, expect(":"), expr);
       return cont(expr);
     }
-    if (type == "quasi") { cx.cc.push(me); return quasi(value); }
+    if (type == "quasi") { return pass(quasi, me); }
     if (type == ";") return;
     if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
     if (type == ".") return cont(property, me);
     if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
   }
-  function quasi(value) {
-    if (value.slice(value.length - 2) != "${") return cont();
+  function quasi(type, value) {
+    if (type != "quasi") return pass();
+    if (value.slice(value.length - 2) != "${") return cont(quasi);
     return cont(expression, continueQuasi);
   }
   function continueQuasi(type) {
     if (type == "}") {
       cx.marked = "string-2";
       cx.state.tokenize = tokenQuasi;
-      return cont();
+      return cont(quasi);
     }
   }
   function arrowBody(type) {
index 1a70cd6..0ab2177 100644 (file)
@@ -33,7 +33,6 @@
   var presto = /Opera\//.test(navigator.userAgent);
   var safari = /Apple Computer/.test(navigator.vendor);
   var khtml = /KHTML\//.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 phantom = /PhantomJS/.test(navigator.userAgent);
 
       d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
     } else d.gutterFiller.style.display = "";
 
-    if (mac_geLion && scrollbarWidth(d.measure) === 0) {
-      d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
-      var barMouseDown = function(e) {
-        if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
-          operation(cm, onMouseDown)(e);
-      };
-      on(d.scrollbarV, "mousedown", barMouseDown);
-      on(d.scrollbarH, "mousedown", barMouseDown);
+    if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
+      if (scrollbarWidth(d.measure) === 0) {
+        var w = mac && !mac_geMountainLion ? "12px" : "18px";
+        d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
+        var barMouseDown = function(e) {
+          if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
+            operation(cm, onMouseDown)(e);
+        };
+        on(d.scrollbarV, "mousedown", barMouseDown);
+        on(d.scrollbarH, "mousedown", barMouseDown);
+      }
+      cm.state.checkedOverlayScrollbar = true;
     }
   }
 
 
   // Set a new selection.
   function setSelection(doc, sel, options) {
-    if (options && options.origin && doc.cm) doc.cm.curOp.origin = options.origin;
     setSelectionNoUndo(doc, sel, options);
     addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
   }
 
     doc.sel = sel;
 
-    if (doc.cm)
-      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
-        doc.cm.curOp.cursorActivity = true;
+    if (doc.cm) {
+      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
+      signalCursorActivity(doc.cm);
+    }
     signalLater(doc, "cursorActivity", doc);
   }
 
     if (display.cachedPaddingH) return display.cachedPaddingH;
     var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
     var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
-    return display.cachedPaddingH = {left: parseInt(style.paddingLeft),
-                                     right: parseInt(style.paddingRight)};
+    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
+    if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
+    return data;
   }
 
   // Ensure the lineView.wrapping.heights array is populated. This is
       updateInput: null,       // Whether to reset the input textarea
       typing: false,           // Whether this reset should be careful to leave existing text (for compositing)
       changeObjs: null,        // Accumulated changes, for firing change events
-      origin: null,            // Selection's origin
-      cursorActivity: false,   // Whether to fire a cursorActivity event
+      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
       selectionChanged: false, // Whether the selection needs to be redrawn
       updateMaxLine: false,    // Set when the widest line needs to be determined anew
       scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
       delayedCallbacks = null;
     }
     // Fire change events, and delayed event handlers
-    if (op.changeObjs) {
-      for (var i = 0; i < op.changeObjs.length; i++)
-        signal(cm, "change", cm, op.changeObjs[i]);
+    if (op.changeObjs)
       signal(cm, "changes", cm, op.changeObjs);
-    }
-    if (op.cursorActivity) signal(cm, "cursorActivity", cm, op.origin);
     if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
+    if (op.cursorActivityHandlers)
+      for (var i = 0; i < op.cursorActivityHandlers.length; i++)
+        op.cursorActivityHandlers[i](cm);
   }
 
   // Run the given function in an operation
     // possible when it is clear that nothing happened. hasSelection
     // will be the case when there is a lot of text in the textarea,
     // in which case reading its value would be expensive.
-    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false;
+    if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput)
+      return false;
+    // See paste handler for more on the fakedLastChar kludge
+    if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
+      input.value = input.value.substring(0, input.value.length - 1);
+      cm.state.fakedLastChar = false;
+    }
     var text = input.value;
     // If nothing changed, bail.
     if (text == prevInput && !cm.somethingSelected()) return false;
       fastPoll(cm);
     });
     on(d.input, "paste", function() {
+      // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
+      // Add a char to the end of textarea before paste occur so that
+      // selection doesn't span to the end of textarea.
+      if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
+        var start = d.input.selectionStart, end = d.input.selectionEnd;
+        d.input.value += "$";
+        d.input.selectionStart = start;
+        d.input.selectionEnd = end;
+        cm.state.fakedLastChar = true;
+      }
       cm.state.pasteIncoming = true;
       fastPoll(cm);
     });
     var coords = coordsChar(cm, x, y), line;
     if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
       var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
-      coords = Pos(coords.line, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff);
+      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
     }
     return coords;
   }
     e_preventDefault(e);
 
     var ourRange, ourIndex, startSel = doc.sel;
-    if (addNew) {
+    if (addNew && !e.shiftKey) {
       ourIndex = doc.sel.contains(start);
       if (ourIndex > -1)
         ourRange = doc.sel.ranges[ourIndex];
     if (!addNew) {
       ourIndex = 0;
       setSelection(doc, new Selection([ourRange], 0), sel_mouse);
+      startSel = doc.sel;
     } else if (ourIndex > -1) {
       replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
     } else {
     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
       return;
     e_preventDefault(e);
-    if (ie_upto10) lastDrop = +new Date;
+    if (ie) lastDrop = +new Date;
     var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
     if (!pos || isReadOnly(cm)) return;
     // Might be a file drop, in which case we simply extract the text
   }
 
   function onDragStart(cm, e) {
-    if (ie_upto10 && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
+    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
     if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
 
     e.dataTransfer.setData("Text", cm.getSelection());
       signal(cm, "focus", cm);
       cm.state.focused = true;
       addClass(cm.display.wrapper, "CodeMirror-focused");
-      if (!cm.curOp) {
+      // The prevInput test prevents this from firing when a context
+      // menu is closed (since the resetInput would kill the
+      // select-all detection hack)
+      if (!cm.curOp && cm.display.selForContextMenu == cm.doc.sel) {
         resetInput(cm);
         if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
       }
     resetInput(cm);
     // Adds "Select all" to context menu in FF
     if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
+    display.selForContextMenu = cm.doc.sel;
 
     // Select-all will be greyed out if there's nothing to select, so
     // this adds a zero-width space so that we can later check whether
     // it got selected.
     function prepareSelectAllHack() {
       if (display.input.selectionStart != null) {
-        var extval = display.input.value = "\u200b" + (cm.somethingSelected() ? display.input.value : "");
-        display.prevInput = "\u200b";
+        var selected = cm.somethingSelected();
+        var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
+        display.prevInput = selected ? "" : "\u200b";
         display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
       }
     }
       if (display.input.selectionStart != null) {
         if (!ie || ie_upto8) prepareSelectAllHack();
         clearTimeout(detectingSelectAll);
-        var i = 0, poll = function(){
-          if (display.prevInput == "\u200b" && display.input.selectionStart == 0)
+        var i = 0, poll = function() {
+          if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
             operation(cm, commands.selectAll)(cm);
           else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
           else resetInput(cm);
     }
 
     if (doc.sel.contains(change.from, change.to) > -1)
-      cm.curOp.cursorActivity = true;
+      signalCursorActivity(cm);
 
     updateDoc(doc, change, spans, estimateHeight(cm));
 
     else
       regChange(cm, from.line, to.line + 1, lendiff);
 
-    if (hasHandler(cm, "change") || hasHandler(cm, "changes"))
-      (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push({
+    var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
+    if (changeHandler || changesHandler) {
+      var obj = {
         from: from, to: to,
         text: change.text,
         removed: change.removed,
         origin: change.origin
-      });
+      };
+      if (changeHandler) signalLater(cm, "change", cm, obj);
+      if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj);
+    }
   }
 
   function replaceRange(doc, code, from, to, origin) {
       pos = clipPos(this.doc, pos);
       var styles = getLineStyles(this, getLine(this.doc, pos.line));
       var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
-      if (ch == 0) return styles[2];
-      for (;;) {
+      var type;
+      if (ch == 0) type = styles[2];
+      else for (;;) {
         var mid = (before + after) >> 1;
         if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
         else if (styles[mid * 2 + 1] < ch) before = mid + 1;
-        else return styles[mid * 2 + 2];
+        else { type = styles[mid * 2 + 2]; break; }
       }
+      var cut = type ? type.indexOf("cm-overlay ") : -1;
+      return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
     },
 
     getModeAt: function(pos) {
         }
         if (!style) return;
         if (overlay.opaque) {
-          st.splice(start, i - start, end, style);
+          st.splice(start, i - start, end, "cm-overlay " + style);
           i = start + 2;
         } else {
           for (; start < i; start += 2) {
             var cur = st[start+1];
-            st[start+1] = cur ? cur + " " + style : style;
+            st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
           }
         }
       }, lineClasses);
       }
       return parts;
     },
-    replaceSelection: docMethodOp(function(code, collapse, origin) {
+    replaceSelection: function(code, collapse, origin) {
       var dup = [];
       for (var i = 0; i < this.sel.ranges.length; i++)
         dup[i] = code;
       this.replaceSelections(dup, collapse, origin || "+input");
-    }),
-    replaceSelections: function(code, collapse, origin) {
+    },
+    replaceSelections: docMethodOp(function(code, collapse, origin) {
       var changes = [], sel = this.sel;
       for (var i = 0; i < sel.ranges.length; i++) {
         var range = sel.ranges[i];
         makeChange(this, changes[i]);
       if (newSel) setSelectionReplaceHistory(this, newSel);
       else if (this.cm) ensureCursorVisible(this.cm);
-    },
+    }),
     undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
     redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
     undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
     return e_defaultPrevented(e) || e.codemirrorIgnore;
   }
 
+  function signalCursorActivity(cm) {
+    var arr = cm._handlers && cm._handlers.cursorActivity;
+    if (!arr) return;
+    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
+    for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
+      set.push(arr[i]);
+  }
+
   function hasHandler(emitter, type) {
     var arr = emitter._handlers && emitter._handlers[type];
     return arr && arr.length > 0;
 
   // THE END
 
-  CodeMirror.version = "4.0.4";
+  CodeMirror.version = "4.1.0";
 
   return CodeMirror;
 });
index 3426732..a2b80df 100644 (file)
@@ -92,7 +92,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
 
   function tokenParenthesized(stream, state) {
     stream.next(); // Must be '('
-    if (!stream.match(/\s*[\"\']/, false))
+    if (!stream.match(/\s*[\"\')]/, false))
       state.tokenize = tokenString(")");
     else
       state.tokenize = null;
@@ -453,13 +453,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
     // SVG-specific
     "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
     "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
-    "color-interpolation", "color-interpolation-filters", "color-profile",
+    "color-interpolation", "color-interpolation-filters",
     "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
     "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
     "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
     "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
     "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
-    "glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode"
+    "glyph-orientation-vertical", "text-anchor", "writing-mode"
   ], propertyKeywords = keySet(propertyKeywords_);
 
   var nonStandardPropertyKeywords = [
index 9318aab..daf138b 100644 (file)
@@ -366,6 +366,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
     if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
     if (type == "{") return contCommasep(objprop, "}", null, maybeop);
+    if (type == "quasi") { return pass(quasi, maybeop); }
     return cont();
   }
   function maybeexpression(type) {
@@ -390,21 +391,22 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
       if (value == "?") return cont(expression, expect(":"), expr);
       return cont(expr);
     }
-    if (type == "quasi") { cx.cc.push(me); return quasi(value); }
+    if (type == "quasi") { return pass(quasi, me); }
     if (type == ";") return;
     if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
     if (type == ".") return cont(property, me);
     if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
   }
-  function quasi(value) {
-    if (value.slice(value.length - 2) != "${") return cont();
+  function quasi(type, value) {
+    if (type != "quasi") return pass();
+    if (value.slice(value.length - 2) != "${") return cont(quasi);
     return cont(expression, continueQuasi);
   }
   function continueQuasi(type) {
     if (type == "}") {
       cx.marked = "string-2";
       cx.state.tokenize = tokenQuasi;
-      return cont();
+      return cont(quasi);
     }
   }
   function arrowBody(type) {
index 9322ba8..756181f 100644 (file)
@@ -24,7 +24,7 @@
           for (i$ = 0, len$ = nr.length; i$ < len$; ++i$) {
             r = nr[i$];
             if (r.regex && (m = stream.match(r.regex))) {
-              state.next = r.next;
+              state.next = r.next || state.next;
               return r.token;
             }
           }
         next: 'start'
       }, {
         token: 'text',
-        regex: '.',
+        regex: '',
         next: 'start'
       }
     ],
     if (Array.isArray(r)) {
       for (var i = 0, len = r.length; i < len; ++i) {
         var rr = r[i];
-        if (rr.regex) {
+        if (typeof rr.regex === 'string') {
           Rules[idx][i].regex = new RegExp('^' + rr.regex);
         }
       }
-    } else if (r.regex) {
+    } else if (typeof rr.regex === 'string') {
       Rules[idx].regex = new RegExp('^' + r.regex);
     }
   }
index 351840e..44c17b1 100644 (file)
@@ -57,6 +57,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
   for (var i = 0, e = lines.length; i < e; ++i) {
     if (i) callback("\n");
     var stream = new CodeMirror.StringStream(lines[i]);
+    if (!stream.string && mode.blankLine) mode.blankLine();
     while (!stream.eol()) {
       var style = mode.token(stream, state);
       callback(stream.current(), style, i, stream.start, state);
index b8c4297..3248c45 100644 (file)
@@ -321,7 +321,10 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
       var context = state.context;
       // Indent multi-line strings (e.g. css).
       if (state.tokenize.isInAttribute) {
-        return state.stringStartCol + 1;
+        if (state.tagStart == state.indented)
+          return state.stringStartCol + 1;
+        else
+          return state.indented + indentUnit;
       }
       if (context && context.noIndent) return CodeMirror.Pass;
       if (state.tokenize != inTag && state.tokenize != inText)