summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js682
1 files changed, 682 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js b/Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js
new file mode 100644
index 000000000..5542b9547
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/CodeMirrorAdditions.js
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+(function () {
+ // By default CodeMirror defines syntax highlighting styles based on token
+ // only and shared styles between modes. This limiting and does not match
+ // what we have done in the Web Inspector. So this modifies the XML, CSS
+ // and JavaScript modes to supply two styles for each token. One for the
+ // token and one with the mode name.
+
+ function tokenizeLinkString(stream, state)
+ {
+ console.assert(state._linkQuoteCharacter !== undefined);
+
+ // Eat the string until the same quote is found that started the string.
+ // If this is unquoted, then eat until whitespace or common parse errors.
+ if (state._linkQuoteCharacter)
+ stream.eatWhile(new RegExp("[^" + state._linkQuoteCharacter + "]"));
+ else
+ stream.eatWhile(/[^\s\u00a0=<>\"\']/);
+
+ // If the stream isn't at the end of line then we found the end quote.
+ // In the case, change _linkTokenize to parse the end of the link next.
+ // Otherwise _linkTokenize will stay as-is to parse more of the link.
+ if (!stream.eol())
+ state._linkTokenize = tokenizeEndOfLinkString;
+
+ return "link";
+ }
+
+ function tokenizeEndOfLinkString(stream, state)
+ {
+ console.assert(state._linkQuoteCharacter !== undefined);
+ console.assert(state._linkBaseStyle);
+
+ // Eat the quote character to style it with the base style.
+ if (state._linkQuoteCharacter)
+ stream.eat(state._linkQuoteCharacter);
+
+ var style = state._linkBaseStyle;
+
+ // Clean up the state.
+ delete state._linkTokenize;
+ delete state._linkQuoteCharacter;
+ delete state._linkBaseStyle;
+ delete state._srcSetTokenizeState;
+
+ return style;
+ }
+
+ function tokenizeSrcSetString(stream, state)
+ {
+ console.assert(state._linkQuoteCharacter !== undefined);
+
+ if (state._srcSetTokenizeState === "link") {
+ // Eat the string until a space, comma, or ending quote.
+ // If this is unquoted, then eat until whitespace or common parse errors.
+ if (state._linkQuoteCharacter)
+ stream.eatWhile(new RegExp("[^\\s," + state._linkQuoteCharacter + "]"));
+ else
+ stream.eatWhile(/[^\s,\u00a0=<>\"\']/);
+ } else {
+ // Eat the string until a comma, or ending quote.
+ // If this is unquoted, then eat until whitespace or common parse errors.
+ stream.eatSpace();
+ if (state._linkQuoteCharacter)
+ stream.eatWhile(new RegExp("[^," + state._linkQuoteCharacter + "]"));
+ else
+ stream.eatWhile(/[^\s\u00a0=<>\"\']/);
+ stream.eatWhile(/[\s,]/);
+ }
+
+ // If the stream isn't at the end of line and we found the end quote
+ // change _linkTokenize to parse the end of the link next. Otherwise
+ // _linkTokenize will stay as-is to parse more of the srcset.
+ if (stream.eol() || (!state._linkQuoteCharacter || stream.peek() === state._linkQuoteCharacter))
+ state._linkTokenize = tokenizeEndOfLinkString;
+
+ // Link portion.
+ if (state._srcSetTokenizeState === "link") {
+ state._srcSetTokenizeState = "descriptor";
+ return "link";
+ }
+
+ // Descriptor portion.
+ state._srcSetTokenizeState = "link";
+ return state._linkBaseStyle;
+ }
+
+ function extendedXMLToken(stream, state)
+ {
+ if (state._linkTokenize) {
+ // Call the link tokenizer instead.
+ var style = state._linkTokenize(stream, state);
+ return style && (style + " m-" + this.name);
+ }
+
+ // Remember the start position so we can rewind if needed.
+ var startPosition = stream.pos;
+ var style = this._token(stream, state);
+ if (style === "attribute") {
+ // Look for "href" or "src" attributes. If found then we should
+ // expect a string later that should get the "link" style instead.
+ var text = stream.current().toLowerCase();
+ if (text === "src" || /\bhref\b/.test(text))
+ state._expectLink = true;
+ else if (text === "srcset")
+ state._expectSrcSet = true;
+ else {
+ delete state._expectLink;
+ delete state._expectSrcSet;
+ }
+ } else if (state._expectLink && style === "string") {
+ var current = stream.current();
+
+ // Unless current token is empty quotes, consume quote character
+ // and tokenize link next.
+ if (current !== "\"\"" && current !== "''") {
+ delete state._expectLink;
+
+ // This is a link, so setup the state to process it next.
+ state._linkTokenize = tokenizeLinkString;
+ state._linkBaseStyle = style;
+
+ // The attribute may or may not be quoted.
+ var quote = current[0];
+
+ state._linkQuoteCharacter = quote === "'" || quote === "\"" ? quote : null;
+
+ // Rewind the stream to the start of this token.
+ stream.pos = startPosition;
+
+ // Eat the open quote of the string so the string style
+ // will be used for the quote character.
+ if (state._linkQuoteCharacter)
+ stream.eat(state._linkQuoteCharacter);
+ }
+ } else if (state._expectSrcSet && style === "string") {
+ var current = stream.current();
+
+ // Unless current token is empty quotes, consume quote character
+ // and tokenize link next.
+ if (current !== "\"\"" && current !== "''") {
+ delete state._expectSrcSet;
+
+ // This is a link, so setup the state to process it next.
+ state._srcSetTokenizeState = "link";
+ state._linkTokenize = tokenizeSrcSetString;
+ state._linkBaseStyle = style;
+
+ // The attribute may or may not be quoted.
+ var quote = current[0];
+
+ state._linkQuoteCharacter = quote === "'" || quote === "\"" ? quote : null;
+
+ // Rewind the stream to the start of this token.
+ stream.pos = startPosition;
+
+ // Eat the open quote of the string so the string style
+ // will be used for the quote character.
+ if (state._linkQuoteCharacter)
+ stream.eat(state._linkQuoteCharacter);
+ }
+ } else if (style) {
+ // We don't expect other tokens between attribute and string since
+ // spaces and the equal character are not tokenized. So if we get
+ // another token before a string then we stop expecting a link.
+ delete state._expectLink;
+ delete state._expectSrcSet;
+ }
+
+ return style && (style + " m-" + this.name);
+ }
+
+ function tokenizeCSSURLString(stream, state)
+ {
+ console.assert(state._urlQuoteCharacter);
+
+ // If we are an unquoted url string, return whitespace blocks as a whitespace token (null).
+ if (state._unquotedURLString && stream.eatSpace())
+ return null;
+
+ var ch = null;
+ var escaped = false;
+ var reachedEndOfURL = false;
+ var lastNonWhitespace = stream.pos;
+ var quote = state._urlQuoteCharacter;
+
+ // Parse characters until the end of the stream/line or a proper end quote character.
+ while ((ch = stream.next()) != null) {
+ if (ch === quote && !escaped) {
+ reachedEndOfURL = true;
+ break;
+ }
+ escaped = !escaped && ch === "\\";
+ if (!/[\s\u00a0]/.test(ch))
+ lastNonWhitespace = stream.pos;
+ }
+
+ // If we are an unquoted url string, do not include trailing whitespace, rewind to the last real character.
+ if (state._unquotedURLString)
+ stream.pos = lastNonWhitespace;
+
+ // If we have reached the proper the end of the url string, switch to the end tokenizer to reset the state.
+ if (reachedEndOfURL) {
+ if (!state._unquotedURLString)
+ stream.backUp(1);
+ this._urlTokenize = tokenizeEndOfCSSURLString;
+ }
+
+ return "link";
+ }
+
+ function tokenizeEndOfCSSURLString(stream, state)
+ {
+ console.assert(state._urlQuoteCharacter);
+ console.assert(state._urlBaseStyle);
+
+ // Eat the quote character to style it with the base style.
+ if (!state._unquotedURLString)
+ stream.eat(state._urlQuoteCharacter);
+
+ var style = state._urlBaseStyle;
+
+ delete state._urlTokenize;
+ delete state._urlQuoteCharacter;
+ delete state._urlBaseStyle;
+
+ return style;
+ }
+
+ function extendedCSSToken(stream, state)
+ {
+ var hexColorRegex = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/g;
+
+ if (state._urlTokenize) {
+ // Call the link tokenizer instead.
+ var style = state._urlTokenize(stream, state);
+ return style && (style + " m-" + (this.alternateName || this.name));
+ }
+
+ // Remember the start position so we can rewind if needed.
+ var startPosition = stream.pos;
+ var style = this._token(stream, state);
+
+ if (style) {
+ if (style === "atom") {
+ if (stream.current() === "url") {
+ // If the current text is "url" then we should expect the next string token to be a link.
+ state._expectLink = true;
+ } else if (hexColorRegex.test(stream.current()))
+ style = style + " hex-color";
+ } else if (state._expectLink) {
+ delete state._expectLink;
+
+ if (style === "string") {
+ // This is a link, so setup the state to process it next.
+ state._urlTokenize = tokenizeCSSURLString;
+ state._urlBaseStyle = style;
+
+ // The url may or may not be quoted.
+ var quote = stream.current()[0];
+ state._urlQuoteCharacter = quote === "'" || quote === "\"" ? quote : ")";
+ state._unquotedURLString = state._urlQuoteCharacter === ")";
+
+ // Rewind the stream to the start of this token.
+ stream.pos = startPosition;
+
+ // Eat the open quote of the string so the string style
+ // will be used for the quote character.
+ if (!state._unquotedURLString)
+ stream.eat(state._urlQuoteCharacter);
+ }
+ }
+ }
+
+ return style && (style + " m-" + (this.alternateName || this.name));
+ }
+
+ function extendedToken(stream, state)
+ {
+ // CodeMirror moves the original token function to _token when we extended it.
+ // So call it to get the style that we will add an additional class name to.
+ var style = this._token(stream, state);
+ return style && (style + " m-" + (this.alternateName || this.name));
+ }
+
+ function extendedCSSRuleStartState(base)
+ {
+ // CodeMirror moves the original token function to _startState when we extended it.
+ // So call it to get the original start state that we will modify.
+ var state = this._startState(base);
+
+ // Start off the state stack like it has already parsed a rule. This causes everything
+ // after to be parsed as properties in a rule.
+ state.state = "block";
+ state.context.type = "block";
+
+ return state;
+ }
+
+ function scrollCursorIntoView(codeMirror, event)
+ {
+ // We don't want to use the default implementation since it can cause massive jumping
+ // when the editor is contained inside overflow elements.
+ event.preventDefault();
+
+ function delayedWork()
+ {
+ // Don't try to scroll unless the editor is focused.
+ if (!codeMirror.getWrapperElement().classList.contains("CodeMirror-focused"))
+ return;
+
+ // The cursor element can contain multiple cursors. The first one is the blinky cursor,
+ // which is the one we want to scroll into view. It can be missing, so check first.
+ var cursorElement = codeMirror.getScrollerElement().getElementsByClassName("CodeMirror-cursor")[0];
+ if (cursorElement)
+ cursorElement.scrollIntoViewIfNeeded(false);
+ }
+
+ // We need to delay this because CodeMirror can fire scrollCursorIntoView as a view is being blurred
+ // and another is being focused. The blurred editor still has the focused state when this event fires.
+ // We don't want to scroll the blurred editor into view, only the focused editor.
+ setTimeout(delayedWork, 0);
+ }
+
+ CodeMirror.extendMode("css", {token: extendedCSSToken});
+ CodeMirror.extendMode("xml", {token: extendedXMLToken});
+ CodeMirror.extendMode("javascript", {token: extendedToken});
+
+ CodeMirror.defineMode("css-rule", CodeMirror.modes.css);
+ CodeMirror.extendMode("css-rule", {token: extendedCSSToken, startState: extendedCSSRuleStartState, alternateName: "css"});
+
+ CodeMirror.defineInitHook(function(codeMirror) {
+ codeMirror.on("scrollCursorIntoView", scrollCursorIntoView);
+ });
+
+ const maximumNeighboringWhitespaceCharacters = 16;
+ CodeMirror.defineOption("showWhitespaceCharacters", false, function(cm, value, old) {
+ if (!value || (old && old !== CodeMirror.Init)) {
+ cm.removeOverlay("whitespace");
+ return;
+ }
+
+ cm.addOverlay({
+ name: "whitespace",
+ token(stream) {
+ if (stream.peek() === " ") {
+ let count = 0;
+ while (count < maximumNeighboringWhitespaceCharacters && stream.peek() === " ") {
+ ++count;
+ stream.next();
+ }
+ return `whitespace whitespace-${count}`;
+ }
+
+ while (!stream.eol() && stream.peek() !== " ")
+ stream.next();
+
+ return null;
+ }
+ });
+ });
+
+ CodeMirror.defineExtension("hasLineClass", function(line, where, className) {
+ // This matches the arguments to addLineClass and removeLineClass.
+ var classProperty = (where === "text" ? "textClass" : (where === "background" ? "bgClass" : "wrapClass"));
+ var lineInfo = this.lineInfo(line);
+ if (!lineInfo)
+ return false;
+
+ if (!lineInfo[classProperty])
+ return false;
+
+ // Test for the simple case.
+ if (lineInfo[classProperty] === className)
+ return true;
+
+ // Do a quick check for the substring. This is faster than a regex, which requires escaping the input first.
+ var index = lineInfo[classProperty].indexOf(className);
+ if (index === -1)
+ return false;
+
+ // Check that it is surrounded by spaces. Add padding spaces first to work with beginning and end of string cases.
+ var paddedClass = " " + lineInfo[classProperty] + " ";
+ return paddedClass.indexOf(" " + className + " ", index) !== -1;
+ });
+
+ CodeMirror.defineExtension("setUniqueBookmark", function(position, options) {
+ var marks = this.findMarksAt(position);
+ for (var i = 0; i < marks.length; ++i) {
+ if (marks[i].__uniqueBookmark) {
+ marks[i].clear();
+ break;
+ }
+ }
+
+ var uniqueBookmark = this.setBookmark(position, options);
+ uniqueBookmark.__uniqueBookmark = true;
+ return uniqueBookmark;
+ });
+
+ CodeMirror.defineExtension("toggleLineClass", function(line, where, className) {
+ if (this.hasLineClass(line, where, className)) {
+ this.removeLineClass(line, where, className);
+ return false;
+ }
+
+ this.addLineClass(line, where, className);
+ return true;
+ });
+
+ CodeMirror.defineExtension("alterNumberInRange", function(amount, startPosition, endPosition, updateSelection) {
+ // We don't try if the range is multiline, pass to another key handler.
+ if (startPosition.line !== endPosition.line)
+ return false;
+
+ if (updateSelection) {
+ // Remember the cursor position/selection.
+ var selectionStart = this.getCursor("start");
+ var selectionEnd = this.getCursor("end");
+ }
+
+ var line = this.getLine(startPosition.line);
+
+ var foundPeriod = false;
+
+ var start = NaN;
+ var end = NaN;
+
+ for (var i = startPosition.ch; i >= 0; --i) {
+ var character = line.charAt(i);
+
+ if (character === ".") {
+ if (foundPeriod)
+ break;
+ foundPeriod = true;
+ } else if (character !== "-" && character !== "+" && isNaN(parseInt(character))) {
+ // Found the end already, just scan backwards.
+ if (i === startPosition.ch) {
+ end = i;
+ continue;
+ }
+
+ break;
+ }
+
+ start = i;
+ }
+
+ if (isNaN(end)) {
+ for (var i = startPosition.ch + 1; i < line.length; ++i) {
+ var character = line.charAt(i);
+
+ if (character === ".") {
+ if (foundPeriod) {
+ end = i;
+ break;
+ }
+
+ foundPeriod = true;
+ } else if (isNaN(parseInt(character))) {
+ end = i;
+ break;
+ }
+
+ end = i + 1;
+ }
+ }
+
+ // No number range found, pass to another key handler.
+ if (isNaN(start) || isNaN(end))
+ return false;
+
+ var number = parseFloat(line.substring(start, end));
+
+ // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
+ // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
+ var alteredNumber = Number((number + amount).toFixed(6));
+ var alteredNumberString = alteredNumber.toString();
+
+ var from = {line: startPosition.line, ch: start};
+ var to = {line: startPosition.line, ch: end};
+
+ this.replaceRange(alteredNumberString, from, to);
+
+ if (updateSelection) {
+ var previousLength = to.ch - from.ch;
+ var newLength = alteredNumberString.length;
+
+ // Fix up the selection so it follows the increase or decrease in the replacement length.
+ // selectionStart/End may the same object if there is no selection. If that is the case
+ // make only one modification to prevent a double adjustment, and keep it a single object
+ // to avoid CodeMirror inadvertently creating an actual selection range.
+ let diff = newLength - previousLength;
+ if (selectionStart === selectionEnd)
+ selectionStart.ch += diff;
+ else {
+ if (selectionStart.ch > from.ch)
+ selectionStart.ch += diff;
+ if (selectionEnd.ch > from.ch)
+ selectionEnd.ch += diff;
+ }
+
+ this.setSelection(selectionStart, selectionEnd);
+ }
+
+ return true;
+ });
+
+ function alterNumber(amount, codeMirror)
+ {
+ function findNumberToken(position)
+ {
+ // CodeMirror includes the unit in the number token, so searching for
+ // number tokens is the best way to get both the number and unit.
+ var token = codeMirror.getTokenAt(position);
+ if (token && token.type && /\bnumber\b/.test(token.type))
+ return token;
+ return null;
+ }
+
+ var position = codeMirror.getCursor("head");
+ var token = findNumberToken(position);
+
+ if (!token) {
+ // If the cursor is at the outside beginning of the token, the previous
+ // findNumberToken wont find it. So check the next column for a number too.
+ position.ch += 1;
+ token = findNumberToken(position);
+ }
+
+ if (!token)
+ return CodeMirror.Pass;
+
+ var foundNumber = codeMirror.alterNumberInRange(amount, {ch: token.start, line: position.line}, {ch: token.end, line: position.line}, true);
+ if (!foundNumber)
+ return CodeMirror.Pass;
+ }
+
+ CodeMirror.defineExtension("rectsForRange", function(range) {
+ var lineRects = [];
+
+ for (var line = range.start.line; line <= range.end.line; ++line) {
+ var lineContent = this.getLine(line);
+
+ var startChar = line === range.start.line ? range.start.ch : (lineContent.length - lineContent.trimLeft().length);
+ var endChar = line === range.end.line ? range.end.ch : lineContent.length;
+ var firstCharCoords = this.cursorCoords({ch: startChar, line});
+ var endCharCoords = this.cursorCoords({ch: endChar, line});
+
+ // Handle line wrapping.
+ if (firstCharCoords.bottom !== endCharCoords.bottom) {
+ var maxY = -Number.MAX_VALUE;
+ for (var ch = startChar; ch <= endChar; ++ch) {
+ var coords = this.cursorCoords({ch, line});
+ if (coords.bottom > maxY) {
+ if (ch > startChar) {
+ var maxX = Math.ceil(this.cursorCoords({ch: ch - 1, line}).right);
+ lineRects.push(new WebInspector.Rect(minX, minY, maxX - minX, maxY - minY));
+ }
+ var minX = Math.floor(coords.left);
+ var minY = Math.floor(coords.top);
+ maxY = Math.ceil(coords.bottom);
+ }
+ }
+ maxX = Math.ceil(coords.right);
+ lineRects.push(new WebInspector.Rect(minX, minY, maxX - minX, maxY - minY));
+ } else {
+ var minX = Math.floor(firstCharCoords.left);
+ var minY = Math.floor(firstCharCoords.top);
+ var maxX = Math.ceil(endCharCoords.right);
+ var maxY = Math.ceil(endCharCoords.bottom);
+ lineRects.push(new WebInspector.Rect(minX, minY, maxX - minX, maxY - minY));
+ }
+ }
+ return lineRects;
+ });
+
+ let mac = WebInspector.Platform.name === "mac";
+
+ CodeMirror.keyMap["default"] = {
+ "Alt-Up": alterNumber.bind(null, 1),
+ "Ctrl-Alt-Up": alterNumber.bind(null, 0.1),
+ "Shift-Alt-Up": alterNumber.bind(null, 10),
+ "Alt-PageUp": alterNumber.bind(null, 10),
+ "Shift-Alt-PageUp": alterNumber.bind(null, 100),
+ "Alt-Down": alterNumber.bind(null, -1),
+ "Ctrl-Alt-Down": alterNumber.bind(null, -0.1),
+ "Shift-Alt-Down": alterNumber.bind(null, -10),
+ "Alt-PageDown": alterNumber.bind(null, -10),
+ "Shift-Alt-PageDown": alterNumber.bind(null, -100),
+ "Cmd-/": "toggleComment",
+ "Cmd-D": "selectNextOccurrence",
+ "Shift-Tab": "indentLess",
+ fallthrough: mac ? "macDefault" : "pcDefault"
+ };
+
+ // Register some extra MIME-types for CodeMirror. These are in addition to the
+ // ones CodeMirror already registers, like text/html, text/javascript, etc.
+ var extraXMLTypes = ["text/xml", "text/xsl"];
+ extraXMLTypes.forEach(function(type) {
+ CodeMirror.defineMIME(type, "xml");
+ });
+
+ var extraHTMLTypes = ["application/xhtml+xml", "image/svg+xml"];
+ extraHTMLTypes.forEach(function(type) {
+ CodeMirror.defineMIME(type, "htmlmixed");
+ });
+
+ var extraJavaScriptTypes = ["text/ecmascript", "application/javascript", "application/ecmascript", "application/x-javascript",
+ "text/x-javascript", "text/javascript1.1", "text/javascript1.2", "text/javascript1.3", "text/jscript", "text/livescript"];
+ extraJavaScriptTypes.forEach(function(type) {
+ CodeMirror.defineMIME(type, "javascript");
+ });
+
+ var extraJSONTypes = ["application/x-json", "text/x-json", "application/vnd.api+json"];
+ extraJSONTypes.forEach(function(type) {
+ CodeMirror.defineMIME(type, {name: "javascript", json: true});
+ });
+})();
+
+WebInspector.compareCodeMirrorPositions = function(a, b)
+{
+ var lineCompare = a.line - b.line;
+ if (lineCompare !== 0)
+ return lineCompare;
+
+ var aColumn = "ch" in a ? a.ch : Number.MAX_VALUE;
+ var bColumn = "ch" in b ? b.ch : Number.MAX_VALUE;
+ return aColumn - bColumn;
+};
+
+WebInspector.walkTokens = function(cm, mode, initialPosition, callback)
+{
+ let state = CodeMirror.copyState(mode, cm.getTokenAt(initialPosition).state);
+ if (state.localState)
+ state = state.localState;
+
+ let lineCount = cm.lineCount();
+ let abort = false;
+ for (let lineNumber = initialPosition.line; !abort && lineNumber < lineCount; ++lineNumber) {
+ let line = cm.getLine(lineNumber);
+ let stream = new CodeMirror.StringStream(line);
+ if (lineNumber === initialPosition.line)
+ stream.start = stream.pos = initialPosition.ch;
+
+ while (!stream.eol()) {
+ let tokenType = mode.token(stream, state);
+ if (!callback(tokenType, stream.current())) {
+ abort = true;
+ break;
+ }
+ stream.start = stream.pos;
+ }
+ }
+
+ if (!abort)
+ callback(null);
+};