Bug 1441225 - Update Codemirror to 5.35.0. r=bgrins draft
authorGabriel Luong <gabriel.luong@gmail.com>
Mon, 26 Feb 2018 16:53:57 -0500
changeset 760139 a41a56d9bcbe6b47d64787383c159c2a7cc9b4a6
parent 760138 bc8ed6e3c7a23e46a3f281ebf6cf7f335d4ee8ea
push id100547
push userbmo:gl@mozilla.com
push dateMon, 26 Feb 2018 21:54:29 +0000
reviewersbgrins
bugs1441225
milestone60.0a1
Bug 1441225 - Update Codemirror to 5.35.0. r=bgrins MozReview-Commit-ID: 8wdTkcemRJt
devtools/client/sourceeditor/README
devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js
devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
devtools/client/sourceeditor/codemirror/codemirror.bundle.js
devtools/client/sourceeditor/codemirror/keymap/sublime.js
devtools/client/sourceeditor/codemirror/keymap/vim.js
devtools/client/sourceeditor/codemirror/lib/codemirror.js
devtools/client/sourceeditor/codemirror/mode/clike/clike.js
devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
devtools/client/sourceeditor/test/codemirror/test.js
devtools/client/sourceeditor/test/codemirror/vim_test.js
--- a/devtools/client/sourceeditor/README
+++ b/devtools/client/sourceeditor/README
@@ -1,16 +1,16 @@
 This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
 is a JavaScript component that provides a code editor in the browser. When
 a mode is available for the language you are coding in, it will color your
 code, and optionally help with indentation.
 
 # Upgrade
 
-Currently used version is 5.34.0. To upgrade: download a new version of
+Currently used version is 5.35.0. To upgrade: download a new version of
 CodeMirror from the project's page [1] and replace all JavaScript and
 CSS files inside the codemirror directory [2].
 
 Then to recreate codemirror.bundle.js:
  > cd devtools/client/sourceeditor
  > npm install
  > webpack
 
--- a/devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js
+++ b/devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js
@@ -85,17 +85,17 @@
     clearTimeout(state.timeout);
     state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
   }
 
   function addOverlay(cm, query, hasBoundary, style) {
     var state = cm.state.matchHighlighter;
     cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
     if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
-      var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
+      var searchFor = hasBoundary ? new RegExp("\\b" + query.replace(/[\\\[+*?(){|^$]/g, "\\$&") + "\\b") : query;
       state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
         {className: "CodeMirror-selection-highlight-scrollbar"});
     }
   }
 
   function removeOverlay(cm) {
     var state = cm.state.matchHighlighter;
     if (state.overlay) {
--- a/devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
+++ b/devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
@@ -14,48 +14,52 @@
 
   function regexpFlags(regexp) {
     var flags = regexp.flags
     return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
       + (regexp.global ? "g" : "")
       + (regexp.multiline ? "m" : "")
   }
 
-  function ensureGlobal(regexp) {
-    return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g")
+  function ensureFlags(regexp, flags) {
+    var current = regexpFlags(regexp), target = current
+    for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
+      target += flags.charAt(i)
+    return current == target ? regexp : new RegExp(regexp.source, target)
   }
 
   function maybeMultiline(regexp) {
     return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
   }
 
   function searchRegexpForward(doc, regexp, start) {
-    regexp = ensureGlobal(regexp)
+    regexp = ensureFlags(regexp, "g")
     for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
       regexp.lastIndex = ch
       var string = doc.getLine(line), match = regexp.exec(string)
       if (match)
         return {from: Pos(line, match.index),
                 to: Pos(line, match.index + match[0].length),
                 match: match}
     }
   }
 
   function searchRegexpForwardMultiline(doc, regexp, start) {
     if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
 
-    regexp = ensureGlobal(regexp)
+    regexp = ensureFlags(regexp, "gm")
     var string, chunk = 1
     for (var line = start.line, last = doc.lastLine(); line <= last;) {
       // This grows the search buffer in exponentially-sized chunks
       // between matches, so that nearby matches are fast and don't
       // require concatenating the whole document (in case we're
       // searching for something that has tons of matches), but at the
       // same time, the amount of retries is limited.
       for (var i = 0; i < chunk; i++) {
+        if (line > last) break
         var curLine = doc.getLine(line++)
         string = string == null ? curLine : string + "\n" + curLine
       }
       chunk = chunk * 2
       regexp.lastIndex = start.ch
       var match = regexp.exec(string)
       if (match) {
         var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
@@ -76,30 +80,30 @@
       if (!newMatch) return match
       match = newMatch
       cutOff = match.index + (match[0].length || 1)
       if (cutOff == string.length) return match
     }
   }
 
   function searchRegexpBackward(doc, regexp, start) {
-    regexp = ensureGlobal(regexp)
+    regexp = ensureFlags(regexp, "g")
     for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
       var string = doc.getLine(line)
       if (ch > -1) string = string.slice(0, ch)
       var match = lastMatchIn(string, regexp)
       if (match)
         return {from: Pos(line, match.index),
                 to: Pos(line, match.index + match[0].length),
                 match: match}
     }
   }
 
   function searchRegexpBackwardMultiline(doc, regexp, start) {
-    regexp = ensureGlobal(regexp)
+    regexp = ensureFlags(regexp, "gm")
     var string, chunk = 1
     for (var line = start.line, first = doc.firstLine(); line >= first;) {
       for (var i = 0; i < chunk; i++) {
         var curLine = doc.getLine(line--)
         string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
       }
       chunk *= 2
 
@@ -208,17 +212,17 @@
     }
 
     if (typeof query == "string") {
       if (caseFold == null) caseFold = false
       this.matches = function(reverse, pos) {
         return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
       }
     } else {
-      query = ensureGlobal(query)
+      query = ensureFlags(query, "gm")
       if (!options || options.multiline !== false)
         this.matches = function(reverse, pos) {
           return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
         }
       else
         this.matches = function(reverse, pos) {
           return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
         }
--- a/devtools/client/sourceeditor/codemirror/codemirror.bundle.js
+++ b/devtools/client/sourceeditor/codemirror/codemirror.bundle.js
@@ -5449,17 +5449,18 @@ var CodeMirror =
 	      rebased.push(doc.history)
 	    }
 	    makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change))
 	  })
 	}
 
 	// Revert a change stored in a document's history.
 	function makeChangeFromHistory(doc, type, allowSelectionOnly) {
-	  if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return }
+	  var suppress = doc.cm && doc.cm.state.suppressEdits
+	  if (suppress && !allowSelectionOnly) { return }
 
 	  var hist = doc.history, event, selAfter = doc.sel
 	  var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done
 
 	  // Verify that there is a useable event (so that ctrl-z won't
 	  // needlessly clear selection events)
 	  var i = 0
 	  for (; i < source.length; i++) {
@@ -5474,18 +5475,20 @@ var CodeMirror =
 	    event = source.pop()
 	    if (event.ranges) {
 	      pushSelectionToHistory(event, dest)
 	      if (allowSelectionOnly && !event.equals(doc.sel)) {
 	        setSelection(doc, event, {clearRedo: false})
 	        return
 	      }
 	      selAfter = event
-	    }
-	    else { break }
+	    } else if (suppress) {
+	      source.push(event)
+	      return
+	    } else { break }
 	  }
 
 	  // Build up a reverse change object to add to the opposite history
 	  // stack (redo when undoing, and vice versa).
 	  var antiChanges = []
 	  pushSelectionToHistory(selAfter, dest)
 	  dest.push({changes: antiChanges, generation: hist.generation})
 	  hist.generation = event.generation || ++hist.maxGeneration
@@ -5951,17 +5954,17 @@ var CodeMirror =
 	    if (cm && !lineIsHidden(doc, line)) {
 	      var aboveVisible = heightAtLine(line) < doc.scrollTop
 	      updateLineHeight(line, line.height + widgetHeight(widget))
 	      if (aboveVisible) { addToScrollTop(cm, widget.height) }
 	      cm.curOp.forceUpdate = true
 	    }
 	    return true
 	  })
-	  signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle))
+	  if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) }
 	  return widget
 	}
 
 	// TEXTMARKERS
 
 	// Created with markText and setBookmark methods. A TextMarker is a
 	// handle that can be used to clear or find a marked position in the
 	// document. Line objects hold arrays (markedSpans) containing
@@ -9887,17 +9890,17 @@ var CodeMirror =
 	CodeMirror.defineDocExtension = function (name, func) {
 	  Doc.prototype[name] = func
 	}
 
 	CodeMirror.fromTextArea = fromTextArea
 
 	addLegacyProps(CodeMirror)
 
-	CodeMirror.version = "5.34.0"
+	CodeMirror.version = "5.35.0"
 
 	return CodeMirror;
 
 	})));
 
 /***/ }),
 /* 3 */
 /***/ (function(module, exports, __webpack_require__) {
@@ -9918,48 +9921,52 @@ var CodeMirror =
 
 	  function regexpFlags(regexp) {
 	    var flags = regexp.flags
 	    return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
 	      + (regexp.global ? "g" : "")
 	      + (regexp.multiline ? "m" : "")
 	  }
 
-	  function ensureGlobal(regexp) {
-	    return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g")
+	  function ensureFlags(regexp, flags) {
+	    var current = regexpFlags(regexp), target = current
+	    for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
+	      target += flags.charAt(i)
+	    return current == target ? regexp : new RegExp(regexp.source, target)
 	  }
 
 	  function maybeMultiline(regexp) {
 	    return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
 	  }
 
 	  function searchRegexpForward(doc, regexp, start) {
-	    regexp = ensureGlobal(regexp)
+	    regexp = ensureFlags(regexp, "g")
 	    for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
 	      regexp.lastIndex = ch
 	      var string = doc.getLine(line), match = regexp.exec(string)
 	      if (match)
 	        return {from: Pos(line, match.index),
 	                to: Pos(line, match.index + match[0].length),
 	                match: match}
 	    }
 	  }
 
 	  function searchRegexpForwardMultiline(doc, regexp, start) {
 	    if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
 
-	    regexp = ensureGlobal(regexp)
+	    regexp = ensureFlags(regexp, "gm")
 	    var string, chunk = 1
 	    for (var line = start.line, last = doc.lastLine(); line <= last;) {
 	      // This grows the search buffer in exponentially-sized chunks
 	      // between matches, so that nearby matches are fast and don't
 	      // require concatenating the whole document (in case we're
 	      // searching for something that has tons of matches), but at the
 	      // same time, the amount of retries is limited.
 	      for (var i = 0; i < chunk; i++) {
+	        if (line > last) break
 	        var curLine = doc.getLine(line++)
 	        string = string == null ? curLine : string + "\n" + curLine
 	      }
 	      chunk = chunk * 2
 	      regexp.lastIndex = start.ch
 	      var match = regexp.exec(string)
 	      if (match) {
 	        var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
@@ -9980,30 +9987,30 @@ var CodeMirror =
 	      if (!newMatch) return match
 	      match = newMatch
 	      cutOff = match.index + (match[0].length || 1)
 	      if (cutOff == string.length) return match
 	    }
 	  }
 
 	  function searchRegexpBackward(doc, regexp, start) {
-	    regexp = ensureGlobal(regexp)
+	    regexp = ensureFlags(regexp, "g")
 	    for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
 	      var string = doc.getLine(line)
 	      if (ch > -1) string = string.slice(0, ch)
 	      var match = lastMatchIn(string, regexp)
 	      if (match)
 	        return {from: Pos(line, match.index),
 	                to: Pos(line, match.index + match[0].length),
 	                match: match}
 	    }
 	  }
 
 	  function searchRegexpBackwardMultiline(doc, regexp, start) {
-	    regexp = ensureGlobal(regexp)
+	    regexp = ensureFlags(regexp, "gm")
 	    var string, chunk = 1
 	    for (var line = start.line, first = doc.firstLine(); line >= first;) {
 	      for (var i = 0; i < chunk; i++) {
 	        var curLine = doc.getLine(line--)
 	        string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
 	      }
 	      chunk *= 2
 
@@ -10112,17 +10119,17 @@ var CodeMirror =
 	    }
 
 	    if (typeof query == "string") {
 	      if (caseFold == null) caseFold = false
 	      this.matches = function(reverse, pos) {
 	        return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
 	      }
 	    } else {
-	      query = ensureGlobal(query)
+	      query = ensureFlags(query, "gm")
 	      if (!options || options.multiline !== false)
 	        this.matches = function(reverse, pos) {
 	          return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
 	        }
 	      else
 	        this.matches = function(reverse, pos) {
 	          return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
 	        }
@@ -11433,16 +11440,17 @@ var CodeMirror =
 	    if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
 	    if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
 	    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
 	    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);
 	    if (type == "new") return cont(maybeTarget(noComma));
+	    if (type == "import") return cont(expression);
 	    return cont();
 	  }
 	  function maybeexpression(type) {
 	    if (type.match(/[;\}\)\],]/)) return pass();
 	    return pass(expression);
 	  }
 
 	  function maybeoperatorComma(type, value) {
@@ -11627,17 +11635,17 @@ var CodeMirror =
 	    }
 	  }
 	  function typearg(type) {
 	    if (type == "variable") return cont(typearg)
 	    else if (type == ":") return cont(typeexpr)
 	  }
 	  function afterType(type, value) {
 	    if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
-	    if (value == "|" || type == ".") return cont(typeexpr)
+	    if (value == "|" || type == "." || value == "&") return cont(typeexpr)
 	    if (type == "[") return cont(expect("]"), afterType)
 	    if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
 	  }
 	  function maybeTypeArgs(_, value) {
 	    if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
 	  }
 	  function typeparam() {
 	    return pass(typeexpr, maybeTypeDefault)
@@ -11670,17 +11678,18 @@ var CodeMirror =
 	    if (value == "=") return cont(expressionNoComma);
 	  }
 	  function vardefCont(type) {
 	    if (type == ",") return cont(vardef);
 	  }
 	  function maybeelse(type, value) {
 	    if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
 	  }
-	  function forspec(type) {
+	  function forspec(type, value) {
+	    if (value == "await") return cont(forspec);
 	    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
 	  }
 	  function forspec1(type) {
 	    if (type == "var") return cont(vardef, expect(";"), forspec2);
 	    if (type == ";") return cont(forspec2);
 	    if (type == "variable") return cont(formaybeinof);
 	    return pass(expression, expect(";"), forspec2);
 	  }
@@ -11759,16 +11768,17 @@ var CodeMirror =
 	    return pass(statement);
 	  }
 	  function exportField(type, value) {
 	    if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
 	    if (type == "variable") return pass(expressionNoComma, exportField);
 	  }
 	  function afterImport(type) {
 	    if (type == "string") return cont();
+	    if (type == "(") return pass(expression);
 	    return pass(importSpec, maybeMoreImports, maybeFrom);
 	  }
 	  function importSpec(type, value) {
 	    if (type == "{") return contCommasep(importSpec, "}");
 	    if (type == "variable") register(value);
 	    if (value == "*") cx.marked = "keyword";
 	    return cont(maybeAs);
 	  }
@@ -14411,33 +14421,33 @@ var CodeMirror =
 	    name: "clike",
 	    keywords: words(cKeywords),
 	    types: words(cTypes + " bool _Complex _Bool float_t double_t intptr_t intmax_t " +
 	                 "int8_t int16_t int32_t int64_t uintptr_t uintmax_t uint8_t uint16_t " +
 	                 "uint32_t uint64_t"),
 	    blockKeywords: words("case do else for if switch while struct"),
 	    defKeywords: words("struct"),
 	    typeFirstDefinitions: true,
-	    atoms: words("null true false"),
+	    atoms: words("NULL true false"),
 	    hooks: {"#": cppHook, "*": pointerHook},
 	    modeProps: {fold: ["brace", "include"]}
 	  });
 
 	  def(["text/x-c++src", "text/x-c++hdr"], {
 	    name: "clike",
 	    keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try explicit new " +
 	                    "static_cast typeid catch operator template typename class friend private " +
 	                    "this using const_cast inline public throw virtual delete mutable protected " +
 	                    "alignas alignof constexpr decltype nullptr noexcept thread_local final " +
 	                    "static_assert override"),
 	    types: words(cTypes + " bool wchar_t"),
 	    blockKeywords: words("catch class do else finally for if struct switch try while"),
 	    defKeywords: words("class namespace struct enum union"),
 	    typeFirstDefinitions: true,
-	    atoms: words("true false null"),
+	    atoms: words("true false NULL"),
 	    dontIndentStatements: /^template$/,
 	    isIdentifierChar: /[\w\$_~\xa1-\uffff]/,
 	    hooks: {
 	      "#": cppHook,
 	      "*": pointerHook,
 	      "u": cpp11StringHook,
 	      "U": cpp11StringHook,
 	      "L": cpp11StringHook,
@@ -14634,32 +14644,34 @@ var CodeMirror =
 	      return "string";
 	    }
 	  }
 
 	  def("text/x-kotlin", {
 	    name: "clike",
 	    keywords: words(
 	      /*keywords*/
-	      "package as typealias class interface this super val " +
-	      "var fun for is in This throw return " +
+	      "package as typealias class interface this super val operator " +
+	      "var fun for is in This throw return annotation " +
 	      "break continue object if else while do try when !in !is as? " +
 
 	      /*soft keywords*/
 	      "file import where by get set abstract enum open inner override private public internal " +
 	      "protected catch finally out final vararg reified dynamic companion constructor init " +
 	      "sealed field property receiver param sparam lateinit data inline noinline tailrec " +
-	      "external annotation crossinline const operator infix suspend actual expect"
+	      "external annotation crossinline const operator infix suspend actual expect setparam"
 	    ),
 	    types: words(
 	      /* package java.lang */
 	      "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
 	      "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
 	      "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
-	      "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
+	      "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " +
+	      "ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " +
+	      "LazyThreadSafetyMode LongArray Nothing ShortArray Unit"
 	    ),
 	    intendSwitch: false,
 	    indentStatements: false,
 	    multiLineStrings: true,
 	    number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
 	    blockKeywords: words("catch class do else finally for if where try while enum"),
 	    defKeywords: words("class val var object interface fun"),
 	    atoms: words("true false null this"),
@@ -16443,17 +16455,17 @@ var CodeMirror =
 	            }
 	            vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop();
 	          }
 	          clearInputState(cm);
 	          return match.command;
 	        }
 
 	        function handleKeyNonInsertMode() {
-	          if (handleMacroRecording() || handleEsc()) { return true; };
+	          if (handleMacroRecording() || handleEsc()) { return true; }
 
 	          var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
 	          if (/^[1-9]\d*$/.test(keys)) { return true; }
 
 	          var keysMatcher = /^(\d*)(.*)$/.exec(keys);
 	          if (!keysMatcher) { clearInputState(cm); return false; }
 	          var context = vim.visualMode ? 'visual' :
 	                                         'normal';
@@ -17028,17 +17040,17 @@ var CodeMirror =
 	          }
 	        }
 	        if (command.type == 'keyToEx') {
 	          // Handle user defined Ex to Ex mappings
 	          exCommandDispatcher.processCommand(cm, command.exArgs.input);
 	        } else {
 	          if (vim.visualMode) {
 	            showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
-	                onKeyDown: onPromptKeyDown});
+	                onKeyDown: onPromptKeyDown, selectValueOnOpen: false});
 	          } else {
 	            showPrompt(cm, { onClose: onPromptClose, prefix: ':',
 	                onKeyDown: onPromptKeyDown});
 	          }
 	        }
 	      },
 	      evalInput: function(cm, vim) {
 	        // If the motion command is set, execute both the operator and motion.
@@ -19285,34 +19297,45 @@ var CodeMirror =
 	            onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,
 	            selectValueOnOpen: false});
 	      }
 	      else {
 	        onClose(prompt(shortText, ''));
 	      }
 	    }
 	    function splitBySlash(argString) {
-	      var slashes = findUnescapedSlashes(argString) || [];
+	      return splitBySeparator(argString, '/');
+	    }
+
+	    function findUnescapedSlashes(argString) {
+	      return findUnescapedSeparators(argString, '/');
+	    }
+
+	    function splitBySeparator(argString, separator) {
+	      var slashes = findUnescapedSeparators(argString, separator) || [];
 	      if (!slashes.length) return [];
 	      var tokens = [];
 	      // in case of strings like foo/bar
 	      if (slashes[0] !== 0) return;
 	      for (var i = 0; i < slashes.length; i++) {
 	        if (typeof slashes[i] == 'number')
 	          tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));
 	      }
 	      return tokens;
 	    }
 
-	    function findUnescapedSlashes(str) {
+	    function findUnescapedSeparators(str, separator) {
+	      if (!separator)
+	        separator = '/';
+
 	      var escapeNextChar = false;
 	      var slashes = [];
 	      for (var i = 0; i < str.length; i++) {
 	        var c = str.charAt(i);
-	        if (!escapeNextChar && c == '/') {
+	        if (!escapeNextChar && c == separator) {
 	          slashes.push(i);
 	        }
 	        escapeNextChar = !escapeNextChar && (c == '\\');
 	      }
 	      return slashes;
 	    }
 
 	    // Translates a search string from ex (vim) syntax into javascript form.
@@ -20168,17 +20191,17 @@ var CodeMirror =
 	        nextCommand();
 	      },
 	      substitute: function(cm, params) {
 	        if (!cm.getSearchCursor) {
 	          throw new Error('Search feature not available. Requires searchcursor.js or ' +
 	              'any other getSearchCursor implementation.');
 	        }
 	        var argString = params.argString;
-	        var tokens = argString ? splitBySlash(argString) : [];
+	        var tokens = argString ? splitBySeparator(argString, argString[0]) : [];
 	        var regexPart, replacePart = '', trailing, flagsPart, count;
 	        var confirm = false; // Whether to confirm each replace.
 	        var global = false; // True to replace all instances on a line, false to replace only 1.
 	        if (tokens.length) {
 	          regexPart = tokens[0];
 	          replacePart = tokens[1];
 	          if (regexPart && regexPart[regexPart.length - 1] === '$') {
 	            regexPart = regexPart.slice(0, regexPart.length - 1) + '\\n';
@@ -20212,17 +20235,17 @@ var CodeMirror =
 	            if (flagsPart.indexOf('c') != -1) {
 	              confirm = true;
 	              flagsPart.replace('c', '');
 	            }
 	            if (flagsPart.indexOf('g') != -1) {
 	              global = true;
 	              flagsPart.replace('g', '');
 	            }
-	            regexPart = regexPart + '/' + flagsPart;
+	            regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;
 	          }
 	        }
 	        if (regexPart) {
 	          // If regex part is empty, then use the previous query. Otherwise use
 	          // the regex part as the new query.
 	          try {
 	            updateSearchQuery(cm, regexPart, true /** ignoreCase */,
 	              true /** smartCase */);
@@ -20428,17 +20451,17 @@ var CodeMirror =
 	      // Actually do replace.
 	      next();
 	      if (done) {
 	        showConfirm(cm, 'No matches for ' + query.source);
 	        return;
 	      }
 	      if (!confirm) {
 	        replaceAll();
-	        if (callback) { callback(); };
+	        if (callback) { callback(); }
 	        return;
 	      }
 	      showPrompt(cm, {
 	        prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
 	        onKeyDown: onPromptKeyDown
 	      });
 	    }
 
@@ -20564,17 +20587,17 @@ var CodeMirror =
 	          if (vim.insertMode) {
 	            var changes = register.insertModeChanges[imc++].changes;
 	            vimGlobalState.macroModeState.lastInsertModeChanges.changes =
 	                changes;
 	            repeatInsertModeChanges(cm, changes, 1);
 	            exitInsertMode(cm);
 	          }
 	        }
-	      };
+	      }
 	      macroModeState.isPlaying = false;
 	    }
 
 	    function logKey(macroModeState, key) {
 	      if (macroModeState.isPlaying) { return; }
 	      var registerName = macroModeState.latestRegister;
 	      var register = vimGlobalState.registerController.getRegister(registerName);
 	      if (register) {
@@ -20768,17 +20791,17 @@ var CodeMirror =
 	      }
 	      vim.inputState = cachedInputState;
 	      if (vim.insertMode && !repeatForInsert) {
 	        // Don't exit insert mode twice. If repeatForInsert is set, then we
 	        // were called by an exitInsertMode call lower on the stack.
 	        exitInsertMode(cm);
 	      }
 	      macroModeState.isPlaying = false;
-	    };
+	    }
 
 	    function repeatInsertModeChanges(cm, changes, repeat) {
 	      function keyHandler(binding) {
 	        if (typeof binding == 'string') {
 	          CodeMirror.commands[binding](cm);
 	        } else {
 	          binding(cm);
 	        }
@@ -20983,18 +21006,24 @@ var CodeMirror =
 	    if (fullWord)
 	      cm.state.sublimeFindFullWord = cm.doc.sel;
 	  };
 
 	  function addCursorToSelection(cm, dir) {
 	    var ranges = cm.listSelections(), newRanges = [];
 	    for (var i = 0; i < ranges.length; i++) {
 	      var range = ranges[i];
-	      var newAnchor = cm.findPosV(range.anchor, dir, "line");
-	      var newHead = cm.findPosV(range.head, dir, "line");
+	      var newAnchor = cm.findPosV(
+	          range.anchor, dir, "line", range.anchor.goalColumn);
+	      var newHead = cm.findPosV(
+	          range.head, dir, "line", range.head.goalColumn);
+	      newAnchor.goalColumn = range.anchor.goalColumn != null ?
+	          range.anchor.goalColumn : cm.cursorCoords(range.anchor, "div").left;
+	      newHead.goalColumn = range.head.goalColumn != null ?
+	          range.head.goalColumn : cm.cursorCoords(range.head, "div").left;
 	      var newRange = {anchor: newAnchor, head: newHead};
 	      newRanges.push(range);
 	      newRanges.push(newRange);
 	    }
 	    cm.setSelections(newRanges);
 	  }
 	  cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };
 	  cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };
--- a/devtools/client/sourceeditor/codemirror/keymap/sublime.js
+++ b/devtools/client/sourceeditor/codemirror/keymap/sublime.js
@@ -151,18 +151,24 @@
     if (fullWord)
       cm.state.sublimeFindFullWord = cm.doc.sel;
   };
 
   function addCursorToSelection(cm, dir) {
     var ranges = cm.listSelections(), newRanges = [];
     for (var i = 0; i < ranges.length; i++) {
       var range = ranges[i];
-      var newAnchor = cm.findPosV(range.anchor, dir, "line");
-      var newHead = cm.findPosV(range.head, dir, "line");
+      var newAnchor = cm.findPosV(
+          range.anchor, dir, "line", range.anchor.goalColumn);
+      var newHead = cm.findPosV(
+          range.head, dir, "line", range.head.goalColumn);
+      newAnchor.goalColumn = range.anchor.goalColumn != null ?
+          range.anchor.goalColumn : cm.cursorCoords(range.anchor, "div").left;
+      newHead.goalColumn = range.head.goalColumn != null ?
+          range.head.goalColumn : cm.cursorCoords(range.head, "div").left;
       var newRange = {anchor: newAnchor, head: newHead};
       newRanges.push(range);
       newRanges.push(newRange);
     }
     cm.setSelections(newRanges);
   }
   cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };
   cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };
--- a/devtools/client/sourceeditor/codemirror/keymap/vim.js
+++ b/devtools/client/sourceeditor/codemirror/keymap/vim.js
@@ -836,17 +836,17 @@
             }
             vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop();
           }
           clearInputState(cm);
           return match.command;
         }
 
         function handleKeyNonInsertMode() {
-          if (handleMacroRecording() || handleEsc()) { return true; };
+          if (handleMacroRecording() || handleEsc()) { return true; }
 
           var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
           if (/^[1-9]\d*$/.test(keys)) { return true; }
 
           var keysMatcher = /^(\d*)(.*)$/.exec(keys);
           if (!keysMatcher) { clearInputState(cm); return false; }
           var context = vim.visualMode ? 'visual' :
                                          'normal';
@@ -1421,17 +1421,17 @@
           }
         }
         if (command.type == 'keyToEx') {
           // Handle user defined Ex to Ex mappings
           exCommandDispatcher.processCommand(cm, command.exArgs.input);
         } else {
           if (vim.visualMode) {
             showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
-                onKeyDown: onPromptKeyDown});
+                onKeyDown: onPromptKeyDown, selectValueOnOpen: false});
           } else {
             showPrompt(cm, { onClose: onPromptClose, prefix: ':',
                 onKeyDown: onPromptKeyDown});
           }
         }
       },
       evalInput: function(cm, vim) {
         // If the motion command is set, execute both the operator and motion.
@@ -3678,34 +3678,45 @@
             onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp,
             selectValueOnOpen: false});
       }
       else {
         onClose(prompt(shortText, ''));
       }
     }
     function splitBySlash(argString) {
-      var slashes = findUnescapedSlashes(argString) || [];
+      return splitBySeparator(argString, '/');
+    }
+
+    function findUnescapedSlashes(argString) {
+      return findUnescapedSeparators(argString, '/');
+    }
+
+    function splitBySeparator(argString, separator) {
+      var slashes = findUnescapedSeparators(argString, separator) || [];
       if (!slashes.length) return [];
       var tokens = [];
       // in case of strings like foo/bar
       if (slashes[0] !== 0) return;
       for (var i = 0; i < slashes.length; i++) {
         if (typeof slashes[i] == 'number')
           tokens.push(argString.substring(slashes[i] + 1, slashes[i+1]));
       }
       return tokens;
     }
 
-    function findUnescapedSlashes(str) {
+    function findUnescapedSeparators(str, separator) {
+      if (!separator)
+        separator = '/';
+
       var escapeNextChar = false;
       var slashes = [];
       for (var i = 0; i < str.length; i++) {
         var c = str.charAt(i);
-        if (!escapeNextChar && c == '/') {
+        if (!escapeNextChar && c == separator) {
           slashes.push(i);
         }
         escapeNextChar = !escapeNextChar && (c == '\\');
       }
       return slashes;
     }
 
     // Translates a search string from ex (vim) syntax into javascript form.
@@ -4561,17 +4572,17 @@
         nextCommand();
       },
       substitute: function(cm, params) {
         if (!cm.getSearchCursor) {
           throw new Error('Search feature not available. Requires searchcursor.js or ' +
               'any other getSearchCursor implementation.');
         }
         var argString = params.argString;
-        var tokens = argString ? splitBySlash(argString) : [];
+        var tokens = argString ? splitBySeparator(argString, argString[0]) : [];
         var regexPart, replacePart = '', trailing, flagsPart, count;
         var confirm = false; // Whether to confirm each replace.
         var global = false; // True to replace all instances on a line, false to replace only 1.
         if (tokens.length) {
           regexPart = tokens[0];
           replacePart = tokens[1];
           if (regexPart && regexPart[regexPart.length - 1] === '$') {
             regexPart = regexPart.slice(0, regexPart.length - 1) + '\\n';
@@ -4605,17 +4616,17 @@
             if (flagsPart.indexOf('c') != -1) {
               confirm = true;
               flagsPart.replace('c', '');
             }
             if (flagsPart.indexOf('g') != -1) {
               global = true;
               flagsPart.replace('g', '');
             }
-            regexPart = regexPart + '/' + flagsPart;
+            regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart;
           }
         }
         if (regexPart) {
           // If regex part is empty, then use the previous query. Otherwise use
           // the regex part as the new query.
           try {
             updateSearchQuery(cm, regexPart, true /** ignoreCase */,
               true /** smartCase */);
@@ -4821,17 +4832,17 @@
       // Actually do replace.
       next();
       if (done) {
         showConfirm(cm, 'No matches for ' + query.source);
         return;
       }
       if (!confirm) {
         replaceAll();
-        if (callback) { callback(); };
+        if (callback) { callback(); }
         return;
       }
       showPrompt(cm, {
         prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
         onKeyDown: onPromptKeyDown
       });
     }
 
@@ -4957,17 +4968,17 @@
           if (vim.insertMode) {
             var changes = register.insertModeChanges[imc++].changes;
             vimGlobalState.macroModeState.lastInsertModeChanges.changes =
                 changes;
             repeatInsertModeChanges(cm, changes, 1);
             exitInsertMode(cm);
           }
         }
-      };
+      }
       macroModeState.isPlaying = false;
     }
 
     function logKey(macroModeState, key) {
       if (macroModeState.isPlaying) { return; }
       var registerName = macroModeState.latestRegister;
       var register = vimGlobalState.registerController.getRegister(registerName);
       if (register) {
@@ -5161,17 +5172,17 @@
       }
       vim.inputState = cachedInputState;
       if (vim.insertMode && !repeatForInsert) {
         // Don't exit insert mode twice. If repeatForInsert is set, then we
         // were called by an exitInsertMode call lower on the stack.
         exitInsertMode(cm);
       }
       macroModeState.isPlaying = false;
-    };
+    }
 
     function repeatInsertModeChanges(cm, changes, repeat) {
       function keyHandler(binding) {
         if (typeof binding == 'string') {
           CodeMirror.commands[binding](cm);
         } else {
           binding(cm);
         }
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
@@ -5207,17 +5207,18 @@ function makeChangeInner(doc, change) {
       rebased.push(doc.history)
     }
     makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change))
   })
 }
 
 // Revert a change stored in a document's history.
 function makeChangeFromHistory(doc, type, allowSelectionOnly) {
-  if (doc.cm && doc.cm.state.suppressEdits && !allowSelectionOnly) { return }
+  var suppress = doc.cm && doc.cm.state.suppressEdits
+  if (suppress && !allowSelectionOnly) { return }
 
   var hist = doc.history, event, selAfter = doc.sel
   var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done
 
   // Verify that there is a useable event (so that ctrl-z won't
   // needlessly clear selection events)
   var i = 0
   for (; i < source.length; i++) {
@@ -5232,18 +5233,20 @@ function makeChangeFromHistory(doc, type
     event = source.pop()
     if (event.ranges) {
       pushSelectionToHistory(event, dest)
       if (allowSelectionOnly && !event.equals(doc.sel)) {
         setSelection(doc, event, {clearRedo: false})
         return
       }
       selAfter = event
-    }
-    else { break }
+    } else if (suppress) {
+      source.push(event)
+      return
+    } else { break }
   }
 
   // Build up a reverse change object to add to the opposite history
   // stack (redo when undoing, and vice versa).
   var antiChanges = []
   pushSelectionToHistory(selAfter, dest)
   dest.push({changes: antiChanges, generation: hist.generation})
   hist.generation = event.generation || ++hist.maxGeneration
@@ -5709,17 +5712,17 @@ function addLineWidget(doc, handle, node
     if (cm && !lineIsHidden(doc, line)) {
       var aboveVisible = heightAtLine(line) < doc.scrollTop
       updateLineHeight(line, line.height + widgetHeight(widget))
       if (aboveVisible) { addToScrollTop(cm, widget.height) }
       cm.curOp.forceUpdate = true
     }
     return true
   })
-  signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle))
+  if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) }
   return widget
 }
 
 // TEXTMARKERS
 
 // Created with markText and setBookmark methods. A TextMarker is a
 // handle that can be used to clear or find a marked position in the
 // document. Line objects hold arrays (markedSpans) containing
@@ -9645,13 +9648,13 @@ CodeMirror.defineExtension = function (n
 CodeMirror.defineDocExtension = function (name, func) {
   Doc.prototype[name] = func
 }
 
 CodeMirror.fromTextArea = fromTextArea
 
 addLegacyProps(CodeMirror)
 
-CodeMirror.version = "5.34.0"
+CodeMirror.version = "5.35.0"
 
 return CodeMirror;
 
 })));
\ No newline at end of file
--- a/devtools/client/sourceeditor/codemirror/mode/clike/clike.js
+++ b/devtools/client/sourceeditor/codemirror/mode/clike/clike.js
@@ -369,33 +369,33 @@ CodeMirror.defineMode("clike", function(
     name: "clike",
     keywords: words(cKeywords),
     types: words(cTypes + " bool _Complex _Bool float_t double_t intptr_t intmax_t " +
                  "int8_t int16_t int32_t int64_t uintptr_t uintmax_t uint8_t uint16_t " +
                  "uint32_t uint64_t"),
     blockKeywords: words("case do else for if switch while struct"),
     defKeywords: words("struct"),
     typeFirstDefinitions: true,
-    atoms: words("null true false"),
+    atoms: words("NULL true false"),
     hooks: {"#": cppHook, "*": pointerHook},
     modeProps: {fold: ["brace", "include"]}
   });
 
   def(["text/x-c++src", "text/x-c++hdr"], {
     name: "clike",
     keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try explicit new " +
                     "static_cast typeid catch operator template typename class friend private " +
                     "this using const_cast inline public throw virtual delete mutable protected " +
                     "alignas alignof constexpr decltype nullptr noexcept thread_local final " +
                     "static_assert override"),
     types: words(cTypes + " bool wchar_t"),
     blockKeywords: words("catch class do else finally for if struct switch try while"),
     defKeywords: words("class namespace struct enum union"),
     typeFirstDefinitions: true,
-    atoms: words("true false null"),
+    atoms: words("true false NULL"),
     dontIndentStatements: /^template$/,
     isIdentifierChar: /[\w\$_~\xa1-\uffff]/,
     hooks: {
       "#": cppHook,
       "*": pointerHook,
       "u": cpp11StringHook,
       "U": cpp11StringHook,
       "L": cpp11StringHook,
@@ -592,32 +592,34 @@ CodeMirror.defineMode("clike", function(
       return "string";
     }
   }
 
   def("text/x-kotlin", {
     name: "clike",
     keywords: words(
       /*keywords*/
-      "package as typealias class interface this super val " +
-      "var fun for is in This throw return " +
+      "package as typealias class interface this super val operator " +
+      "var fun for is in This throw return annotation " +
       "break continue object if else while do try when !in !is as? " +
 
       /*soft keywords*/
       "file import where by get set abstract enum open inner override private public internal " +
       "protected catch finally out final vararg reified dynamic companion constructor init " +
       "sealed field property receiver param sparam lateinit data inline noinline tailrec " +
-      "external annotation crossinline const operator infix suspend actual expect"
+      "external annotation crossinline const operator infix suspend actual expect setparam"
     ),
     types: words(
       /* package java.lang */
       "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
       "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
       "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
-      "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
+      "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " +
+      "ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " +
+      "LazyThreadSafetyMode LongArray Nothing ShortArray Unit"
     ),
     intendSwitch: false,
     indentStatements: false,
     multiLineStrings: true,
     number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
     blockKeywords: words("catch class do else finally for if where try while enum"),
     defKeywords: words("class val var object interface fun"),
     atoms: words("true false null this"),
--- a/devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
+++ b/devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
@@ -395,16 +395,17 @@ CodeMirror.defineMode("javascript", func
     if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
     if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
     if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
     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);
     if (type == "new") return cont(maybeTarget(noComma));
+    if (type == "import") return cont(expression);
     return cont();
   }
   function maybeexpression(type) {
     if (type.match(/[;\}\)\],]/)) return pass();
     return pass(expression);
   }
 
   function maybeoperatorComma(type, value) {
@@ -589,17 +590,17 @@ CodeMirror.defineMode("javascript", func
     }
   }
   function typearg(type) {
     if (type == "variable") return cont(typearg)
     else if (type == ":") return cont(typeexpr)
   }
   function afterType(type, value) {
     if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
-    if (value == "|" || type == ".") return cont(typeexpr)
+    if (value == "|" || type == "." || value == "&") return cont(typeexpr)
     if (type == "[") return cont(expect("]"), afterType)
     if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
   }
   function maybeTypeArgs(_, value) {
     if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
   }
   function typeparam() {
     return pass(typeexpr, maybeTypeDefault)
@@ -632,17 +633,18 @@ CodeMirror.defineMode("javascript", func
     if (value == "=") return cont(expressionNoComma);
   }
   function vardefCont(type) {
     if (type == ",") return cont(vardef);
   }
   function maybeelse(type, value) {
     if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
   }
-  function forspec(type) {
+  function forspec(type, value) {
+    if (value == "await") return cont(forspec);
     if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
   }
   function forspec1(type) {
     if (type == "var") return cont(vardef, expect(";"), forspec2);
     if (type == ";") return cont(forspec2);
     if (type == "variable") return cont(formaybeinof);
     return pass(expression, expect(";"), forspec2);
   }
@@ -721,16 +723,17 @@ CodeMirror.defineMode("javascript", func
     return pass(statement);
   }
   function exportField(type, value) {
     if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
     if (type == "variable") return pass(expressionNoComma, exportField);
   }
   function afterImport(type) {
     if (type == "string") return cont();
+    if (type == "(") return pass(expression);
     return pass(importSpec, maybeMoreImports, maybeFrom);
   }
   function importSpec(type, value) {
     if (type == "{") return contCommasep(importSpec, "}");
     if (type == "variable") register(value);
     if (value == "*") cx.marked = "keyword";
     return cont(maybeAs);
   }
--- a/devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
+++ b/devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
@@ -58,24 +58,33 @@
      "[keyword function] [def foo]() {",
      "  [keyword import] [def $] [keyword from] [string 'jquery'];",
      "  [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];",
      "}");
 
   MT("import_trailing_comma",
      "[keyword import] {[def foo], [def bar],} [keyword from] [string 'baz']")
 
+  MT("import_dynamic",
+     "[keyword import]([string 'baz']).[property then]")
+
+  MT("import_dynamic",
+     "[keyword const] [def t] [operator =] [keyword import]([string 'baz']).[property then]")
+
   MT("const",
      "[keyword function] [def f]() {",
      "  [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];",
      "}");
 
   MT("for/of",
      "[keyword for]([keyword let] [def of] [keyword of] [variable something]) {}");
 
+  MT("for await",
+     "[keyword for] [keyword await]([keyword let] [def of] [keyword of] [variable something]) {}");
+
   MT("generator",
      "[keyword function*] [def repeat]([def n]) {",
      "  [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])",
      "    [keyword yield] [variable-2 i];",
      "}");
 
   MT("quotedStringAddition",
      "[keyword let] [def f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];");
--- a/devtools/client/sourceeditor/test/codemirror/test.js
+++ b/devtools/client/sourceeditor/test/codemirror/test.js
@@ -2325,17 +2325,17 @@ testCM("lineSeparator", function(cm) {
   eq(cm.getValue("\n"), "foo\nbar\n\nbaz\nquux");
   cm.setOption("lineSeparator", null);
   cm.setValue("foo\nbar\r\nbaz\rquux");
   eq(cm.lineCount(), 4);
 }, {value: "foo\nbar\r\nbaz\rquux",
     lineSeparator: "\n"});
 
 var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/
-var getChar = function (noExtending) { var res; do {res = String.fromCharCode(Math.floor(Math.random()*0x8ac)); } while ([0x90].includes(res.charCodeAt(0)) || (noExtending && extendingChars.test(res))); return res }
+var getChar = function (noExtending) { var res; do {res = String.fromCharCode(Math.floor(Math.random()*0x8ac)); } while ([0x90].indexOf(res.charCodeAt(0)) != -1 || (noExtending && extendingChars.test(res))); return res }
 var getString = function (n) { var res = getChar(true); while (--n > 0) res += getChar(); return res }
 
 function makeItWrapAfter(cm, pos) {
   var firstLineTop = cm.cursorCoords(Pos(0, 0)).top;
   for(var w = 0, posTop; posTop != firstLineTop; ++w) {
     cm.setSize(w);
     posTop = cm.charCoords(pos).top;
   }
@@ -2351,17 +2351,17 @@ function testMoveBidi(str) {
   testCM("move_bidi_" + str, function(cm) {
     if (cm.getOption("inputStyle") != "textarea" || !cm.getOption("rtlMoveVisually")) return;
     cm.getScrollerElement().style.fontFamily = "monospace";
     makeItWrapAfter(cm, Pos(0, 5));
 
     var steps = str.length - countIf(str.split(""), function(ch) { return extendingChars.test(ch) });
     var lineBreaks = {}
     lineBreaks[6 - countIf(str.substr(0, 5).split(""), function(ch) { return extendingChars.test(ch) })] = 'w';
-    if (str.includes("\n")) {
+    if (str.indexOf("\n") != -1) {
       lineBreaks[steps - 2] = 'n';
     }
 
     // Make sure we are at the visual beginning of the first line
     cm.execCommand("goLineStart");
 
     var prevCoords = cm.cursorCoords(), coords;
     for(var i = 0; i < steps; ++i) {
--- a/devtools/client/sourceeditor/test/codemirror/vim_test.js
+++ b/devtools/client/sourceeditor/test/codemirror/vim_test.js
@@ -148,17 +148,17 @@ function testVim(name, run, opts, expect
         }
         for (var i = 0; i < arguments.length; i++) {
           var key = arguments[i];
           // Find key in keymap and handle.
           var handled = CodeMirror.lookupKey(key, cm.getOption('keyMap'), executeHandler, cm);
           // Record for insert mode.
           if (handled == "handled" && cm.state.vim.insertMode && arguments[i] != 'Esc') {
             var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges;
-            if (lastChange && (key.includes('Delete') || key.includes('Backspace'))) {
+            if (lastChange && (key.indexOf('Delete') != -1 || key.indexOf('Backspace') != -1)) {
               lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key));
             }
           }
         }
       }
     }
     function doExFn(cm) {
       return function(command) {
@@ -250,17 +250,17 @@ function testJumplist(name, keys, endPos
   startPos = makeCursor(startPos[0], startPos[1]);
   testVim(name, function(cm, vim, helpers) {
     CodeMirror.Vim.resetVimGlobalState_();
     if(dialog)cm.openDialog = helpers.fakeOpenDialog('word');
     cm.setCursor(startPos);
     helpers.doKeys.apply(null, keys);
     helpers.assertCursorAt(endPos);
   }, {value: jumplistScene});
-};
+}
 testJumplist('jumplist_H', ['H', '<C-o>'], [5,2], [5,2]);
 testJumplist('jumplist_M', ['M', '<C-o>'], [2,2], [2,2]);
 testJumplist('jumplist_L', ['L', '<C-o>'], [2,2], [2,2]);
 testJumplist('jumplist_[[', ['[', '[', '<C-o>'], [5,2], [5,2]);
 testJumplist('jumplist_]]', [']', ']', '<C-o>'], [2,2], [2,2]);
 testJumplist('jumplist_G', ['G', '<C-o>'], [5,2], [5,2]);
 testJumplist('jumplist_gg', ['g', 'g', '<C-o>'], [5,2], [5,2]);
 testJumplist('jumplist_%', ['%', '<C-o>'], [1,5], [1,5]);
@@ -294,25 +294,25 @@ function testMotion(name, keys, endPos, 
   testVim(name, function(cm, vim, helpers) {
     if (!startPos) {
       startPos = new Pos(0, 0);
     }
     cm.setCursor(startPos);
     helpers.doKeys(keys);
     helpers.assertCursorAt(endPos);
   });
-};
+}
 
 function makeCursor(line, ch) {
   return new Pos(line, ch);
-};
+}
 
 function offsetCursor(cur, offsetLine, offsetCh) {
   return new Pos(cur.line + offsetLine, cur.ch + offsetCh);
-};
+}
 
 // Motion tests
 testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4));
 testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4));
 testMotion('h', 'h', makeCursor(0, 0), word1.start);
 testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end);
 testMotion('l', 'l', makeCursor(0, 1));
 testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2));
@@ -3643,16 +3643,21 @@ testVim('ex_global_confirm', function(cm
   eq('one two\n two two\n one one\n two one\n one one', cm.getValue());
 }, {value: 'one one\n one one\n one one\n one one\n one one'});
 // Basic substitute tests.
 testVim('ex_substitute_same_line', function(cm, vim, helpers) {
   cm.setCursor(1, 0);
   helpers.doEx('s/one/two/g');
   eq('one one\n two two', cm.getValue());
 }, { value: 'one one\n one one'});
+testVim('ex_substitute_alternate_separator', function(cm, vim, helpers) {
+  cm.setCursor(1, 0);
+  helpers.doEx('s#o/e#two#g');
+  eq('o/e o/e\n two two', cm.getValue());
+}, { value: 'o/e o/e\n o/e o/e'});
 testVim('ex_substitute_full_file', function(cm, vim, helpers) {
   cm.setCursor(1, 0);
   helpers.doEx('%s/one/two/g');
   eq('two two\n two two', cm.getValue());
 }, { value: 'one one\n one one'});
 testVim('ex_substitute_input_range', function(cm, vim, helpers) {
   cm.setCursor(1, 0);
   helpers.doEx('1,3s/\\d/0/g');
@@ -3899,17 +3904,17 @@ function testSubstituteConfirm(name, com
     } catch(e) {
       throw e
     } finally {
       // Restore overridden functions.
       CodeMirror.keyName = savedKeyName;
       cm.openDialog = savedOpenDialog;
     }
   }, { value: initialValue });
-};
+}
 testSubstituteConfirm('ex_substitute_confirm_emptydoc',
     '%s/x/b/c', '', '', '', makeCursor(0, 0));
 testSubstituteConfirm('ex_substitute_confirm_nomatch',
     '%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0));
 testSubstituteConfirm('ex_substitute_confirm_accept',
     '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1));
 testSubstituteConfirm('ex_substitute_confirm_random_keys',
     '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1));