This is an automated email from the ASF dual-hosted git repository.

amaranhao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/main by this push:
     new f6e0f895 Syntax highlighting for Mango queries  (#1403)
f6e0f895 is described below

commit f6e0f89559060705cc34bf0e2ea733d89d23d29b
Author: Antonio Maranhao <[email protected]>
AuthorDate: Tue Jul 18 09:50:22 2023 -0400

    Syntax highlighting for Mango queries  (#1403)
---
 app/addons/components/components/ace-mode-mango.js |  305 ++++
 .../components/components/ace-webpack-resolvers.js |    2 +
 .../components/components/ace-worker-mango.js      | 1660 ++++++++++++++++++++
 app/addons/components/components/codeeditor.js     |    4 +-
 app/addons/documents/assets/scss/view-editor.scss  |    3 +
 .../documents/mango/components/MangoQueryEditor.js |    2 +-
 jest-config.json                                   |    2 +
 7 files changed, 1975 insertions(+), 3 deletions(-)

diff --git a/app/addons/components/components/ace-mode-mango.js 
b/app/addons/components/components/ace-mode-mango.js
new file mode 100644
index 00000000..f4099afe
--- /dev/null
+++ b/app/addons/components/components/ace-mode-mango.js
@@ -0,0 +1,305 @@
+/* eslint-disable */
+
+// This file is a modified version of 
"node_modules/ace-builds/src-noconflict/mode-json.js"
+// with extra syntax highlighting rules for Mango Query keywords.
+// The "ace/mode/mango_highlight_rules" module defines the highlighting rules. 
It is mostly the same as
+// "mode-json.js" except that the 'token: "variable"' rule was changed to tag 
Mango Query operators
+// as 'keyword.operator' instead.
+
+ace.define("ace/mode/mango_highlight_rules", ["require", "exports", "module", 
"ace/lib/oop", "ace/mode/text_highlight_rules"], function(require, exports, 
module) {
+  "use strict";
+  var oop = require("../lib/oop");
+  var TextHighlightRules = 
require("./text_highlight_rules").TextHighlightRules;
+  var options = ["selector", "limit", "skip", "sort", "fields", "use_index", 
"fields", "conflicts", "r", "bookmark", "update", "stable", "stale", 
"execution_stats"];
+  var operators = [
+    // value operators
+    "$gte", "$gt", "$lte", "$lt", "$eq", "$ne", "$type", "$size", "$regex", 
"$exists", "$mod",
+    // list operators
+    "$in", "$nin", "$all",
+    // selector combination operators
+    "$and", "$or", "$nor",
+    // selector operators
+    "$not",
+    // selector condition operators
+    "$elemMatch", "$allMatch", "$keyMapMatch"
+  ];
+  var MangoHighlightRules = function () {
+    this.$rules = {
+      "start": [
+        {
+          //token: "variable",
+          token: function(value) {
+            if (operators.includes(value.substr(1, value.length - 2))) {
+              return "keyword.operator";
+            } else if (options.includes(value.substr(1, value.length - 2))) {
+              return "keyword.operator";
+            }
+            return "variable";
+          },
+          regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)'
+        }, {
+          token: "string",
+          regex: '"',
+          next: "string"
+        }, {
+          token: "constant.numeric",
+          regex: "0[xX][0-9a-fA-F]+\\b"
+        }, {
+          token: "constant.numeric",
+          regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
+        }, {
+          token: "constant.language.boolean",
+          regex: "(?:true|false)\\b"
+        }, {
+          token: "text",
+          regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
+        }, {
+          token: "comment",
+          regex: "\\/\\/.*$"
+        }, {
+          token: "comment.start",
+          regex: "\\/\\*",
+          next: "comment"
+        }, {
+          token: "paren.lparen",
+          regex: "[[({]"
+        }, {
+          token: "paren.rparen",
+          regex: "[\\])}]"
+        }, {
+          token: "punctuation.operator",
+          regex: /[,]/
+        }, {
+          token: "text",
+          regex: "\\s+"
+        }
+      ],
+      "string": [
+        {
+          token: "constant.language.escape",
+          regex: /\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|["\\\/bfnrt])/
+        }, {
+          token: "string",
+          regex: '"|$',
+          next: "start"
+        }, {
+          defaultToken: "string"
+        }
+      ],
+      "comment": [
+        {
+          token: "comment.end",
+          regex: "\\*\\/",
+          next: "start"
+        }, {
+          defaultToken: "comment"
+        }
+      ]
+    };
+  };
+  oop.inherits(MangoHighlightRules, TextHighlightRules);
+  exports.MangoHighlightRules = MangoHighlightRules;
+
+});
+
+ace.define("ace/mode/matching_brace_outdent", ["require", "exports", "module", 
"ace/range"], function(require, exports, module) {
+  "use strict";
+  var Range = require("../range").Range;
+  var MatchingBraceOutdent = function () { };
+  (function () {
+    this.checkOutdent = function (line, input) {
+      if (!/^\s+$/.test(line))
+        return false;
+      return /^\s*\}/.test(input);
+    };
+    this.autoOutdent = function (doc, row) {
+      var line = doc.getLine(row);
+      var match = line.match(/^(\s*\})/);
+      if (!match)
+        return 0;
+      var column = match[1].length;
+      var openBracePos = doc.findMatchingBracket({ row: row, column: column });
+      if (!openBracePos || openBracePos.row == row)
+        return 0;
+      var indent = this.$getIndent(doc.getLine(openBracePos.row));
+      doc.replace(new Range(row, 0, row, column - 1), indent);
+    };
+    this.$getIndent = function (line) {
+      return line.match(/^\s*/)[0];
+    };
+  }).call(MatchingBraceOutdent.prototype);
+  exports.MatchingBraceOutdent = MatchingBraceOutdent;
+
+});
+
+ace.define("ace/mode/folding/cstyle", ["require", "exports", "module", 
"ace/lib/oop", "ace/range", "ace/mode/folding/fold_mode"], function(require, 
exports, module) {
+  "use strict";
+  var oop = require("../../lib/oop");
+  var Range = require("../../range").Range;
+  var BaseFoldMode = require("./fold_mode").FoldMode;
+  var FoldMode = exports.FoldMode = function (commentRegex) {
+    if (commentRegex) {
+      this.foldingStartMarker = new 
RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + 
commentRegex.start));
+      this.foldingStopMarker = new 
RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + 
commentRegex.end));
+    }
+  };
+  oop.inherits(FoldMode, BaseFoldMode);
+  (function () {
+    this.foldingStartMarker = /([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/;
+    this.foldingStopMarker = /^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/;
+    this.singleLineBlockCommentRe = /^\s*(\/\*).*\*\/\s*$/;
+    this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/;
+    this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/;
+    this._getFoldWidgetBase = this.getFoldWidget;
+    this.getFoldWidget = function (session, foldStyle, row) {
+      var line = session.getLine(row);
+      if (this.singleLineBlockCommentRe.test(line)) {
+        if (!this.startRegionRe.test(line) && 
!this.tripleStarBlockCommentRe.test(line))
+          return "";
+      }
+      var fw = this._getFoldWidgetBase(session, foldStyle, row);
+      if (!fw && this.startRegionRe.test(line))
+        return "start"; // lineCommentRegionStart
+      return fw;
+    };
+    this.getFoldWidgetRange = function (session, foldStyle, row, 
forceMultiline) {
+      var line = session.getLine(row);
+      if (this.startRegionRe.test(line))
+        return this.getCommentRegionBlock(session, line, row);
+      var match = line.match(this.foldingStartMarker);
+      if (match) {
+        var i = match.index;
+        if (match[1])
+          return this.openingBracketBlock(session, match[1], row, i);
+        var range = session.getCommentFoldRange(row, i + match[0].length, 1);
+        if (range && !range.isMultiLine()) {
+          if (forceMultiline) {
+            range = this.getSectionRange(session, row);
+          } else if (foldStyle != "all")
+            range = null;
+        }
+        return range;
+      }
+      if (foldStyle === "markbegin")
+        return;
+      var match = line.match(this.foldingStopMarker);
+      if (match) {
+        var i = match.index + match[0].length;
+        if (match[1])
+          return this.closingBracketBlock(session, match[1], row, i);
+        return session.getCommentFoldRange(row, i, -1);
+      }
+    };
+    this.getSectionRange = function (session, row) {
+      var line = session.getLine(row);
+      var startIndent = line.search(/\S/);
+      var startRow = row;
+      var startColumn = line.length;
+      row = row + 1;
+      var endRow = row;
+      var maxRow = session.getLength();
+      while (++row < maxRow) {
+        line = session.getLine(row);
+        var indent = line.search(/\S/);
+        if (indent === -1)
+          continue;
+        if (startIndent > indent)
+          break;
+        var subRange = this.getFoldWidgetRange(session, "all", row);
+        if (subRange) {
+          if (subRange.start.row <= startRow) {
+            break;
+          } else if (subRange.isMultiLine()) {
+            row = subRange.end.row;
+          } else if (startIndent == indent) {
+            break;
+          }
+        }
+        endRow = row;
+      }
+      return new Range(startRow, startColumn, endRow, 
session.getLine(endRow).length);
+    };
+    this.getCommentRegionBlock = function (session, line, row) {
+      var startColumn = line.search(/\s*$/);
+      var maxRow = session.getLength();
+      var startRow = row;
+      var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/;
+      var depth = 1;
+      while (++row < maxRow) {
+        line = session.getLine(row);
+        var m = re.exec(line);
+        if (!m)
+          continue;
+        if (m[1])
+          depth--;
+        else
+          depth++;
+        if (!depth)
+          break;
+      }
+      var endRow = row;
+      if (endRow > startRow) {
+        return new Range(startRow, startColumn, endRow, line.length);
+      }
+    };
+  }).call(FoldMode.prototype);
+
+});
+
+ace.define("ace/mode/mango", ["require", "exports", "module", "ace/lib/oop", 
"ace/mode/text", "ace/mode/mango_highlight_rules", 
"ace/mode/matching_brace_outdent", "ace/mode/behaviour/cstyle", 
"ace/mode/folding/cstyle", "ace/worker/worker_client"], function(require, 
exports, module) {
+  "use strict";
+  var oop = require("../lib/oop");
+  var TextMode = require("./text").Mode;
+  var HighlightRules = require("./mango_highlight_rules").MangoHighlightRules;
+  var MatchingBraceOutdent = 
require("./matching_brace_outdent").MatchingBraceOutdent;
+  var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour;
+  var CStyleFoldMode = require("./folding/cstyle").FoldMode;
+  var WorkerClient = require("../worker/worker_client").WorkerClient;
+  var Mode = function () {
+    this.HighlightRules = HighlightRules;
+    this.$outdent = new MatchingBraceOutdent();
+    this.$behaviour = new CstyleBehaviour();
+    this.foldingRules = new CStyleFoldMode();
+  };
+  oop.inherits(Mode, TextMode);
+  (function () {
+    this.lineCommentStart = "//";
+    this.blockComment = { start: "/*", end: "*/" };
+    this.getNextLineIndent = function (state, line, tab) {
+      var indent = this.$getIndent(line);
+      if (state == "start") {
+        var match = line.match(/^.*[\{\(\[]\s*$/);
+        if (match) {
+          indent += tab;
+        }
+      }
+      return indent;
+    };
+    this.checkOutdent = function (state, line, input) {
+      return this.$outdent.checkOutdent(line, input);
+    };
+    this.autoOutdent = function (state, doc, row) {
+      this.$outdent.autoOutdent(doc, row);
+    };
+    this.createWorker = function (session) {
+      var worker = new WorkerClient(["ace"], "ace/mode/mango_worker", 
"MangoWorker");
+      worker.attachToDocument(session.getDocument());
+      worker.on("annotate", function (e) {
+        session.setAnnotations(e.data);
+      });
+      worker.on("terminate", function () {
+        session.clearAnnotations();
+      });
+      return worker;
+    };
+    this.$id = "ace/mode/mango";
+  }).call(Mode.prototype);
+  exports.Mode = Mode;
+
+});                (function() {
+  ace.require(["ace/mode/mango"], function(m) {
+    if (typeof module == "object" && typeof exports == "object" && module) {
+      module.exports = m;
+    }
+  });
+})();
diff --git a/app/addons/components/components/ace-webpack-resolvers.js 
b/app/addons/components/components/ace-webpack-resolvers.js
index 8bf53a7e..460ff879 100644
--- a/app/addons/components/components/ace-webpack-resolvers.js
+++ b/app/addons/components/components/ace-webpack-resolvers.js
@@ -10,7 +10,9 @@ ace.config.setModuleUrl('ace/theme/idle_fingers', 
require('file-loader?esModule=
 ace.config.setModuleUrl('ace/theme/dawn', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/theme-dawn.js'));
 ace.config.setModuleUrl('ace/mode/json', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/mode-json.js'));
 ace.config.setModuleUrl('ace/mode/json_worker', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/worker-json.js'));
+ace.config.setModuleUrl('ace/mode/mango_worker', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!./ace-worker-mango.js'));
 ace.config.setModuleUrl('ace/mode/javascript', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/mode-javascript.js'));
 ace.config.setModuleUrl('ace/mode/javascript_worker', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/worker-javascript.js'));
 ace.config.setModuleUrl('ace/ext/static_highlight', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/ext-static_highlight.js'));
 ace.config.setModuleUrl('ace/ext/searchbox', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!ace-builds/src-noconflict/ext-searchbox.js'));
+ace.config.setModuleUrl('ace/mode/mango', 
require('file-loader?esModule=false&outputPath=dashboard.assets/js!./ace-mode-mango.js'));
diff --git a/app/addons/components/components/ace-worker-mango.js 
b/app/addons/components/components/ace-worker-mango.js
new file mode 100644
index 00000000..50d9275b
--- /dev/null
+++ b/app/addons/components/components/ace-worker-mango.js
@@ -0,0 +1,1660 @@
+/* eslint-disable */
+
+// This file is a modified version of 
"node_modules/ace-builds/src-noconflict/worker-json.js".
+// It extends the original implementation to also emit "warning" annotations 
when it finds Mango 
+// operators that can have an adverse effect on query performance.
+// Hint: search for 'addWarning()' to see the implemented warnings.
+
+"no use strict";
+!(function(window) {
+// If condition below was added to fix Jest tests.
+if (typeof window === "undefined")
+       return;
+if (typeof window.window != "undefined" && window.document)
+    return;
+if (window.require && window.define)
+    return;
+
+if (!window.console) {
+    window.console = function() {
+        var msgs = Array.prototype.slice.call(arguments, 0);
+        postMessage({type: "log", data: msgs});
+    };
+    window.console.error =
+    window.console.warn = 
+    window.console.log =
+    window.console.trace = window.console;
+}
+window.window = window;
+window.ace = window;
+
+window.onerror = function(message, file, line, col, err) {
+    postMessage({type: "error", data: {
+        message: message,
+        data: err && err.data,
+        file: file,
+        line: line, 
+        col: col,
+        stack: err && err.stack
+    }});
+};
+
+window.normalizeModule = function(parentId, moduleName) {
+    // normalize plugin requires
+    if (moduleName.indexOf("!") !== -1) {
+        var chunks = moduleName.split("!");
+        return window.normalizeModule(parentId, chunks[0]) + "!" + 
window.normalizeModule(parentId, chunks[1]);
+    }
+    // normalize relative requires
+    if (moduleName.charAt(0) == ".") {
+        var base = parentId.split("/").slice(0, -1).join("/");
+        moduleName = (base ? base + "/" : "") + moduleName;
+        
+        while (moduleName.indexOf(".") !== -1 && previous != moduleName) {
+            var previous = moduleName;
+            moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, 
"/").replace(/[^\/]+\/\.\.\//, "");
+        }
+    }
+    
+    return moduleName;
+};
+
+window.require = function require(parentId, id) {
+    if (!id) {
+        id = parentId;
+        parentId = null;
+    }
+    if (!id.charAt)
+        throw new Error("worker.js require() accepts only (parentId, id) as 
arguments");
+
+    id = window.normalizeModule(parentId, id);
+
+    var module = window.require.modules[id];
+    if (module) {
+        if (!module.initialized) {
+            module.initialized = true;
+            module.exports = module.factory().exports;
+        }
+        return module.exports;
+    }
+   
+    if (!window.require.tlns)
+        return console.log("unable to load " + id);
+    
+    var path = resolveModuleId(id, window.require.tlns);
+    if (path.slice(-3) != ".js") path += ".js";
+    
+    window.require.id = id;
+    window.require.modules[id] = {}; // prevent infinite loop on broken modules
+    importScripts(path);
+    return window.require(parentId, id);
+};
+function resolveModuleId(id, paths) {
+    var testPath = id, tail = "";
+    while (testPath) {
+        var alias = paths[testPath];
+        if (typeof alias == "string") {
+            return alias + tail;
+        } else if (alias) {
+            return  alias.location.replace(/\/*$/, "/") + (tail || alias.main 
|| alias.name);
+        } else if (alias === false) {
+            return "";
+        }
+        var i = testPath.lastIndexOf("/");
+        if (i === -1) break;
+        tail = testPath.substr(i) + tail;
+        testPath = testPath.slice(0, i);
+    }
+    return id;
+}
+window.require.modules = {};
+window.require.tlns = {};
+
+window.define = function(id, deps, factory) {
+    if (arguments.length == 2) {
+        factory = deps;
+        if (typeof id != "string") {
+            deps = id;
+            id = window.require.id;
+        }
+    } else if (arguments.length == 1) {
+        factory = id;
+        deps = [];
+        id = window.require.id;
+    }
+    
+    if (typeof factory != "function") {
+        window.require.modules[id] = {
+            exports: factory,
+            initialized: true
+        };
+        return;
+    }
+
+    if (!deps.length)
+        // If there is no dependencies, we inject "require", "exports" and
+        // "module" as dependencies, to provide CommonJS compatibility.
+        deps = ["require", "exports", "module"];
+
+    var req = function(childId) {
+        return window.require(id, childId);
+    };
+
+    window.require.modules[id] = {
+        exports: {},
+        factory: function() {
+            var module = this;
+            var returnExports = factory.apply(this, deps.slice(0, 
factory.length).map(function(dep) {
+                switch (dep) {
+                    // Because "require", "exports" and "module" aren't actual
+                    // dependencies, we must handle them seperately.
+                    case "require": return req;
+                    case "exports": return module.exports;
+                    case "module":  return module;
+                    // But for all other dependencies, we can just go ahead and
+                    // require them.
+                    default:        return req(dep);
+                }
+            }));
+            if (returnExports)
+                module.exports = returnExports;
+            return module;
+        }
+    };
+};
+window.define.amd = {};
+window.require.tlns = {};
+window.initBaseUrls  = function initBaseUrls(topLevelNamespaces) {
+    for (var i in topLevelNamespaces)
+        this.require.tlns[i] = topLevelNamespaces[i];
+};
+
+window.initSender = function initSender() {
+
+    var EventEmitter = window.require("ace/lib/event_emitter").EventEmitter;
+    var oop = window.require("ace/lib/oop");
+    
+    var Sender = function() {};
+    
+    (function() {
+        
+        oop.implement(this, EventEmitter);
+                
+        this.callback = function(data, callbackId) {
+            postMessage({
+                type: "call",
+                id: callbackId,
+                data: data
+            });
+        };
+    
+        this.emit = function(name, data) {
+            postMessage({
+                type: "event",
+                name: name,
+                data: data
+            });
+        };
+        
+    }).call(Sender.prototype);
+    
+    return new Sender();
+};
+
+var main = window.main = null;
+var sender = window.sender = null;
+
+window.onmessage = function(e) {
+    var msg = e.data;
+    if (msg.event && sender) {
+        sender._signal(msg.event, msg.data);
+    }
+    else if (msg.command) {
+        if (main[msg.command])
+            main[msg.command].apply(main, msg.args);
+        else if (window[msg.command])
+            window[msg.command].apply(window, msg.args);
+        else
+            throw new Error("Unknown command:" + msg.command);
+    }
+    else if (msg.init) {
+        window.initBaseUrls(msg.tlns);
+        sender = window.sender = window.initSender();
+        var clazz = this.require(msg.module)[msg.classname];
+        main = window.main = new clazz(sender);
+    }
+};
+})(this);
+
+ace.define("ace/lib/oop",[], function(require, exports, module){"use strict";
+exports.inherits = function (ctor, superCtor) {
+    ctor.super_ = superCtor;
+    ctor.prototype = Object.create(superCtor.prototype, {
+        constructor: {
+            value: ctor,
+            enumerable: false,
+            writable: true,
+            configurable: true
+        }
+    });
+};
+exports.mixin = function (obj, mixin) {
+    for (var key in mixin) {
+        obj[key] = mixin[key];
+    }
+    return obj;
+};
+exports.implement = function (proto, mixin) {
+    exports.mixin(proto, mixin);
+};
+
+});
+
+ace.define("ace/apply_delta",[], function(require, exports, module){"use 
strict";
+function throwDeltaError(delta, errorText) {
+    console.log("Invalid Delta:", delta);
+    throw "Invalid Delta: " + errorText;
+}
+function positionInDocument(docLines, position) {
+    return position.row >= 0 && position.row < docLines.length &&
+        position.column >= 0 && position.column <= 
docLines[position.row].length;
+}
+function validateDelta(docLines, delta) {
+    if (delta.action != "insert" && delta.action != "remove")
+        throwDeltaError(delta, "delta.action must be 'insert' or 'remove'");
+    if (!(delta.lines instanceof Array))
+        throwDeltaError(delta, "delta.lines must be an Array");
+    if (!delta.start || !delta.end)
+        throwDeltaError(delta, "delta.start/end must be an present");
+    var start = delta.start;
+    if (!positionInDocument(docLines, delta.start))
+        throwDeltaError(delta, "delta.start must be contained in document");
+    var end = delta.end;
+    if (delta.action == "remove" && !positionInDocument(docLines, end))
+        throwDeltaError(delta, "delta.end must contained in document for 
'remove' actions");
+    var numRangeRows = end.row - start.row;
+    var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? 
start.column : 0));
+    if (numRangeRows != delta.lines.length - 1 || 
delta.lines[numRangeRows].length != numRangeLastLineChars)
+        throwDeltaError(delta, "delta.range must match delta lines");
+}
+exports.applyDelta = function (docLines, delta, doNotValidate) {
+    var row = delta.start.row;
+    var startColumn = delta.start.column;
+    var line = docLines[row] || "";
+    switch (delta.action) {
+        case "insert":
+            var lines = delta.lines;
+            if (lines.length === 1) {
+                docLines[row] = line.substring(0, startColumn) + 
delta.lines[0] + line.substring(startColumn);
+            }
+            else {
+                var args = [row, 1].concat(delta.lines);
+                docLines.splice.apply(docLines, args);
+                docLines[row] = line.substring(0, startColumn) + docLines[row];
+                docLines[row + delta.lines.length - 1] += 
line.substring(startColumn);
+            }
+            break;
+        case "remove":
+            var endColumn = delta.end.column;
+            var endRow = delta.end.row;
+            if (row === endRow) {
+                docLines[row] = line.substring(0, startColumn) + 
line.substring(endColumn);
+            }
+            else {
+                docLines.splice(row, endRow - row + 1, line.substring(0, 
startColumn) + docLines[endRow].substring(endColumn));
+            }
+            break;
+    }
+};
+
+});
+
+ace.define("ace/lib/event_emitter",[], function(require, exports, module){"use 
strict";
+var EventEmitter = {};
+var stopPropagation = function () { this.propagationStopped = true; };
+var preventDefault = function () { this.defaultPrevented = true; };
+EventEmitter._emit =
+    EventEmitter._dispatchEvent = function (eventName, e) {
+        this._eventRegistry || (this._eventRegistry = {});
+        this._defaultHandlers || (this._defaultHandlers = {});
+        var listeners = this._eventRegistry[eventName] || [];
+        var defaultHandler = this._defaultHandlers[eventName];
+        if (!listeners.length && !defaultHandler)
+            return;
+        if (typeof e != "object" || !e)
+            e = {};
+        if (!e.type)
+            e.type = eventName;
+        if (!e.stopPropagation)
+            e.stopPropagation = stopPropagation;
+        if (!e.preventDefault)
+            e.preventDefault = preventDefault;
+        listeners = listeners.slice();
+        for (var i = 0; i < listeners.length; i++) {
+            listeners[i](e, this);
+            if (e.propagationStopped)
+                break;
+        }
+        if (defaultHandler && !e.defaultPrevented)
+            return defaultHandler(e, this);
+    };
+EventEmitter._signal = function (eventName, e) {
+    var listeners = (this._eventRegistry || {})[eventName];
+    if (!listeners)
+        return;
+    listeners = listeners.slice();
+    for (var i = 0; i < listeners.length; i++)
+        listeners[i](e, this);
+};
+EventEmitter.once = function (eventName, callback) {
+    var _self = this;
+    this.on(eventName, function newCallback() {
+        _self.off(eventName, newCallback);
+        callback.apply(null, arguments);
+    });
+    if (!callback) {
+        return new Promise(function (resolve) {
+            callback = resolve;
+        });
+    }
+};
+EventEmitter.setDefaultHandler = function (eventName, callback) {
+    var handlers = this._defaultHandlers;
+    if (!handlers)
+        handlers = this._defaultHandlers = { _disabled_: {} };
+    if (handlers[eventName]) {
+        var old = handlers[eventName];
+        var disabled = handlers._disabled_[eventName];
+        if (!disabled)
+            handlers._disabled_[eventName] = disabled = [];
+        disabled.push(old);
+        var i = disabled.indexOf(callback);
+        if (i != -1)
+            disabled.splice(i, 1);
+    }
+    handlers[eventName] = callback;
+};
+EventEmitter.removeDefaultHandler = function (eventName, callback) {
+    var handlers = this._defaultHandlers;
+    if (!handlers)
+        return;
+    var disabled = handlers._disabled_[eventName];
+    if (handlers[eventName] == callback) {
+        if (disabled)
+            this.setDefaultHandler(eventName, disabled.pop());
+    }
+    else if (disabled) {
+        var i = disabled.indexOf(callback);
+        if (i != -1)
+            disabled.splice(i, 1);
+    }
+};
+EventEmitter.on =
+    EventEmitter.addEventListener = function (eventName, callback, capturing) {
+        this._eventRegistry = this._eventRegistry || {};
+        var listeners = this._eventRegistry[eventName];
+        if (!listeners)
+            listeners = this._eventRegistry[eventName] = [];
+        if (listeners.indexOf(callback) == -1)
+            listeners[capturing ? "unshift" : "push"](callback);
+        return callback;
+    };
+EventEmitter.off =
+    EventEmitter.removeListener =
+        EventEmitter.removeEventListener = function (eventName, callback) {
+            this._eventRegistry = this._eventRegistry || {};
+            var listeners = this._eventRegistry[eventName];
+            if (!listeners)
+                return;
+            var index = listeners.indexOf(callback);
+            if (index !== -1)
+                listeners.splice(index, 1);
+        };
+EventEmitter.removeAllListeners = function (eventName) {
+    if (!eventName)
+        this._eventRegistry = this._defaultHandlers = undefined;
+    if (this._eventRegistry)
+        this._eventRegistry[eventName] = undefined;
+    if (this._defaultHandlers)
+        this._defaultHandlers[eventName] = undefined;
+};
+exports.EventEmitter = EventEmitter;
+
+});
+
+ace.define("ace/range",[], function(require, exports, module){"use strict";
+var comparePoints = function (p1, p2) {
+    return p1.row - p2.row || p1.column - p2.column;
+};
+var Range = function (startRow, startColumn, endRow, endColumn) {
+    this.start = {
+        row: startRow,
+        column: startColumn
+    };
+    this.end = {
+        row: endRow,
+        column: endColumn
+    };
+};
+(function () {
+    this.isEqual = function (range) {
+        return this.start.row === range.start.row &&
+            this.end.row === range.end.row &&
+            this.start.column === range.start.column &&
+            this.end.column === range.end.column;
+    };
+    this.toString = function () {
+        return ("Range: [" + this.start.row + "/" + this.start.column +
+            "] -> [" + this.end.row + "/" + this.end.column + "]");
+    };
+    this.contains = function (row, column) {
+        return this.compare(row, column) == 0;
+    };
+    this.compareRange = function (range) {
+        var cmp, end = range.end, start = range.start;
+        cmp = this.compare(end.row, end.column);
+        if (cmp == 1) {
+            cmp = this.compare(start.row, start.column);
+            if (cmp == 1) {
+                return 2;
+            }
+            else if (cmp == 0) {
+                return 1;
+            }
+            else {
+                return 0;
+            }
+        }
+        else if (cmp == -1) {
+            return -2;
+        }
+        else {
+            cmp = this.compare(start.row, start.column);
+            if (cmp == -1) {
+                return -1;
+            }
+            else if (cmp == 1) {
+                return 42;
+            }
+            else {
+                return 0;
+            }
+        }
+    };
+    this.comparePoint = function (p) {
+        return this.compare(p.row, p.column);
+    };
+    this.containsRange = function (range) {
+        return this.comparePoint(range.start) == 0 && 
this.comparePoint(range.end) == 0;
+    };
+    this.intersects = function (range) {
+        var cmp = this.compareRange(range);
+        return (cmp == -1 || cmp == 0 || cmp == 1);
+    };
+    this.isEnd = function (row, column) {
+        return this.end.row == row && this.end.column == column;
+    };
+    this.isStart = function (row, column) {
+        return this.start.row == row && this.start.column == column;
+    };
+    this.setStart = function (row, column) {
+        if (typeof row == "object") {
+            this.start.column = row.column;
+            this.start.row = row.row;
+        }
+        else {
+            this.start.row = row;
+            this.start.column = column;
+        }
+    };
+    this.setEnd = function (row, column) {
+        if (typeof row == "object") {
+            this.end.column = row.column;
+            this.end.row = row.row;
+        }
+        else {
+            this.end.row = row;
+            this.end.column = column;
+        }
+    };
+    this.inside = function (row, column) {
+        if (this.compare(row, column) == 0) {
+            if (this.isEnd(row, column) || this.isStart(row, column)) {
+                return false;
+            }
+            else {
+                return true;
+            }
+        }
+        return false;
+    };
+    this.insideStart = function (row, column) {
+        if (this.compare(row, column) == 0) {
+            if (this.isEnd(row, column)) {
+                return false;
+            }
+            else {
+                return true;
+            }
+        }
+        return false;
+    };
+    this.insideEnd = function (row, column) {
+        if (this.compare(row, column) == 0) {
+            if (this.isStart(row, column)) {
+                return false;
+            }
+            else {
+                return true;
+            }
+        }
+        return false;
+    };
+    this.compare = function (row, column) {
+        if (!this.isMultiLine()) {
+            if (row === this.start.row) {
+                return column < this.start.column ? -1 : (column > 
this.end.column ? 1 : 0);
+            }
+        }
+        if (row < this.start.row)
+            return -1;
+        if (row > this.end.row)
+            return 1;
+        if (this.start.row === row)
+            return column >= this.start.column ? 0 : -1;
+        if (this.end.row === row)
+            return column <= this.end.column ? 0 : 1;
+        return 0;
+    };
+    this.compareStart = function (row, column) {
+        if (this.start.row == row && this.start.column == column) {
+            return -1;
+        }
+        else {
+            return this.compare(row, column);
+        }
+    };
+    this.compareEnd = function (row, column) {
+        if (this.end.row == row && this.end.column == column) {
+            return 1;
+        }
+        else {
+            return this.compare(row, column);
+        }
+    };
+    this.compareInside = function (row, column) {
+        if (this.end.row == row && this.end.column == column) {
+            return 1;
+        }
+        else if (this.start.row == row && this.start.column == column) {
+            return -1;
+        }
+        else {
+            return this.compare(row, column);
+        }
+    };
+    this.clipRows = function (firstRow, lastRow) {
+        if (this.end.row > lastRow)
+            var end = { row: lastRow + 1, column: 0 };
+        else if (this.end.row < firstRow)
+            var end = { row: firstRow, column: 0 };
+        if (this.start.row > lastRow)
+            var start = { row: lastRow + 1, column: 0 };
+        else if (this.start.row < firstRow)
+            var start = { row: firstRow, column: 0 };
+        return Range.fromPoints(start || this.start, end || this.end);
+    };
+    this.extend = function (row, column) {
+        var cmp = this.compare(row, column);
+        if (cmp == 0)
+            return this;
+        else if (cmp == -1)
+            var start = { row: row, column: column };
+        else
+            var end = { row: row, column: column };
+        return Range.fromPoints(start || this.start, end || this.end);
+    };
+    this.isEmpty = function () {
+        return (this.start.row === this.end.row && this.start.column === 
this.end.column);
+    };
+    this.isMultiLine = function () {
+        return (this.start.row !== this.end.row);
+    };
+    this.clone = function () {
+        return Range.fromPoints(this.start, this.end);
+    };
+    this.collapseRows = function () {
+        if (this.end.column == 0)
+            return new Range(this.start.row, 0, Math.max(this.start.row, 
this.end.row - 1), 0);
+        else
+            return new Range(this.start.row, 0, this.end.row, 0);
+    };
+    this.toScreenRange = function (session) {
+        var screenPosStart = session.documentToScreenPosition(this.start);
+        var screenPosEnd = session.documentToScreenPosition(this.end);
+        return new Range(screenPosStart.row, screenPosStart.column, 
screenPosEnd.row, screenPosEnd.column);
+    };
+    this.moveBy = function (row, column) {
+        this.start.row += row;
+        this.start.column += column;
+        this.end.row += row;
+        this.end.column += column;
+    };
+}).call(Range.prototype);
+Range.fromPoints = function (start, end) {
+    return new Range(start.row, start.column, end.row, end.column);
+};
+Range.comparePoints = comparePoints;
+Range.comparePoints = function (p1, p2) {
+    return p1.row - p2.row || p1.column - p2.column;
+};
+exports.Range = Range;
+
+});
+
+ace.define("ace/anchor",[], function(require, exports, module){"use strict";
+var oop = require("./lib/oop");
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var Anchor = exports.Anchor = function (doc, row, column) {
+    this.$onChange = this.onChange.bind(this);
+    this.attach(doc);
+    if (typeof column == "undefined")
+        this.setPosition(row.row, row.column);
+    else
+        this.setPosition(row, column);
+};
+(function () {
+    oop.implement(this, EventEmitter);
+    this.getPosition = function () {
+        return this.$clipPositionToDocument(this.row, this.column);
+    };
+    this.getDocument = function () {
+        return this.document;
+    };
+    this.$insertRight = false;
+    this.onChange = function (delta) {
+        if (delta.start.row == delta.end.row && delta.start.row != this.row)
+            return;
+        if (delta.start.row > this.row)
+            return;
+        var point = $getTransformedPoint(delta, { row: this.row, column: 
this.column }, this.$insertRight);
+        this.setPosition(point.row, point.column, true);
+    };
+    function $pointsInOrder(point1, point2, equalPointsInOrder) {
+        var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column 
: point1.column < point2.column;
+        return (point1.row < point2.row) || (point1.row == point2.row && 
bColIsAfter);
+    }
+    function $getTransformedPoint(delta, point, moveIfEqual) {
+        var deltaIsInsert = delta.action == "insert";
+        var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - 
delta.start.row);
+        var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - 
delta.start.column);
+        var deltaStart = delta.start;
+        var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse 
insert range.
+        if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
+            return {
+                row: point.row,
+                column: point.column
+            };
+        }
+        if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
+            return {
+                row: point.row + deltaRowShift,
+                column: point.column + (point.row == deltaEnd.row ? 
deltaColShift : 0)
+            };
+        }
+        return {
+            row: deltaStart.row,
+            column: deltaStart.column
+        };
+    }
+    this.setPosition = function (row, column, noClip) {
+        var pos;
+        if (noClip) {
+            pos = {
+                row: row,
+                column: column
+            };
+        }
+        else {
+            pos = this.$clipPositionToDocument(row, column);
+        }
+        if (this.row == pos.row && this.column == pos.column)
+            return;
+        var old = {
+            row: this.row,
+            column: this.column
+        };
+        this.row = pos.row;
+        this.column = pos.column;
+        this._signal("change", {
+            old: old,
+            value: pos
+        });
+    };
+    this.detach = function () {
+        this.document.off("change", this.$onChange);
+    };
+    this.attach = function (doc) {
+        this.document = doc || this.document;
+        this.document.on("change", this.$onChange);
+    };
+    this.$clipPositionToDocument = function (row, column) {
+        var pos = {};
+        if (row >= this.document.getLength()) {
+            pos.row = Math.max(0, this.document.getLength() - 1);
+            pos.column = this.document.getLine(pos.row).length;
+        }
+        else if (row < 0) {
+            pos.row = 0;
+            pos.column = 0;
+        }
+        else {
+            pos.row = row;
+            pos.column = Math.min(this.document.getLine(pos.row).length, 
Math.max(0, column));
+        }
+        if (column < 0)
+            pos.column = 0;
+        return pos;
+    };
+}).call(Anchor.prototype);
+
+});
+
+ace.define("ace/document",[], function(require, exports, module){"use strict";
+var oop = require("./lib/oop");
+var applyDelta = require("./apply_delta").applyDelta;
+var EventEmitter = require("./lib/event_emitter").EventEmitter;
+var Range = require("./range").Range;
+var Anchor = require("./anchor").Anchor;
+var Document = function (textOrLines) {
+    this.$lines = [""];
+    if (textOrLines.length === 0) {
+        this.$lines = [""];
+    }
+    else if (Array.isArray(textOrLines)) {
+        this.insertMergedLines({ row: 0, column: 0 }, textOrLines);
+    }
+    else {
+        this.insert({ row: 0, column: 0 }, textOrLines);
+    }
+};
+(function () {
+    oop.implement(this, EventEmitter);
+    this.setValue = function (text) {
+        var len = this.getLength() - 1;
+        this.remove(new Range(0, 0, len, this.getLine(len).length));
+        this.insert({ row: 0, column: 0 }, text);
+    };
+    this.getValue = function () {
+        return this.getAllLines().join(this.getNewLineCharacter());
+    };
+    this.createAnchor = function (row, column) {
+        return new Anchor(this, row, column);
+    };
+    if ("aaa".split(/a/).length === 0) {
+        this.$split = function (text) {
+            return text.replace(/\r\n|\r/g, "\n").split("\n");
+        };
+    }
+    else {
+        this.$split = function (text) {
+            return text.split(/\r\n|\r|\n/);
+        };
+    }
+    this.$detectNewLine = function (text) {
+        var match = text.match(/^.*?(\r\n|\r|\n)/m);
+        this.$autoNewLine = match ? match[1] : "\n";
+        this._signal("changeNewLineMode");
+    };
+    this.getNewLineCharacter = function () {
+        switch (this.$newLineMode) {
+            case "windows":
+                return "\r\n";
+            case "unix":
+                return "\n";
+            default:
+                return this.$autoNewLine || "\n";
+        }
+    };
+    this.$autoNewLine = "";
+    this.$newLineMode = "auto";
+    this.setNewLineMode = function (newLineMode) {
+        if (this.$newLineMode === newLineMode)
+            return;
+        this.$newLineMode = newLineMode;
+        this._signal("changeNewLineMode");
+    };
+    this.getNewLineMode = function () {
+        return this.$newLineMode;
+    };
+    this.isNewLine = function (text) {
+        return (text == "\r\n" || text == "\r" || text == "\n");
+    };
+    this.getLine = function (row) {
+        return this.$lines[row] || "";
+    };
+    this.getLines = function (firstRow, lastRow) {
+        return this.$lines.slice(firstRow, lastRow + 1);
+    };
+    this.getAllLines = function () {
+        return this.getLines(0, this.getLength());
+    };
+    this.getLength = function () {
+        return this.$lines.length;
+    };
+    this.getTextRange = function (range) {
+        return this.getLinesForRange(range).join(this.getNewLineCharacter());
+    };
+    this.getLinesForRange = function (range) {
+        var lines;
+        if (range.start.row === range.end.row) {
+            lines = 
[this.getLine(range.start.row).substring(range.start.column, range.end.column)];
+        }
+        else {
+            lines = this.getLines(range.start.row, range.end.row);
+            lines[0] = (lines[0] || "").substring(range.start.column);
+            var l = lines.length - 1;
+            if (range.end.row - range.start.row == l)
+                lines[l] = lines[l].substring(0, range.end.column);
+        }
+        return lines;
+    };
+    this.insertLines = function (row, lines) {
+        console.warn("Use of document.insertLines is deprecated. Use the 
insertFullLines method instead.");
+        return this.insertFullLines(row, lines);
+    };
+    this.removeLines = function (firstRow, lastRow) {
+        console.warn("Use of document.removeLines is deprecated. Use the 
removeFullLines method instead.");
+        return this.removeFullLines(firstRow, lastRow);
+    };
+    this.insertNewLine = function (position) {
+        console.warn("Use of document.insertNewLine is deprecated. Use 
insertMergedLines(position, ['', '']) instead.");
+        return this.insertMergedLines(position, ["", ""]);
+    };
+    this.insert = function (position, text) {
+        if (this.getLength() <= 1)
+            this.$detectNewLine(text);
+        return this.insertMergedLines(position, this.$split(text));
+    };
+    this.insertInLine = function (position, text) {
+        var start = this.clippedPos(position.row, position.column);
+        var end = this.pos(position.row, position.column + text.length);
+        this.applyDelta({
+            start: start,
+            end: end,
+            action: "insert",
+            lines: [text]
+        }, true);
+        return this.clonePos(end);
+    };
+    this.clippedPos = function (row, column) {
+        var length = this.getLength();
+        if (row === undefined) {
+            row = length;
+        }
+        else if (row < 0) {
+            row = 0;
+        }
+        else if (row >= length) {
+            row = length - 1;
+            column = undefined;
+        }
+        var line = this.getLine(row);
+        if (column == undefined)
+            column = line.length;
+        column = Math.min(Math.max(column, 0), line.length);
+        return { row: row, column: column };
+    };
+    this.clonePos = function (pos) {
+        return { row: pos.row, column: pos.column };
+    };
+    this.pos = function (row, column) {
+        return { row: row, column: column };
+    };
+    this.$clipPosition = function (position) {
+        var length = this.getLength();
+        if (position.row >= length) {
+            position.row = Math.max(0, length - 1);
+            position.column = this.getLine(length - 1).length;
+        }
+        else {
+            position.row = Math.max(0, position.row);
+            position.column = Math.min(Math.max(position.column, 0), 
this.getLine(position.row).length);
+        }
+        return position;
+    };
+    this.insertFullLines = function (row, lines) {
+        row = Math.min(Math.max(row, 0), this.getLength());
+        var column = 0;
+        if (row < this.getLength()) {
+            lines = lines.concat([""]);
+            column = 0;
+        }
+        else {
+            lines = [""].concat(lines);
+            row--;
+            column = this.$lines[row].length;
+        }
+        this.insertMergedLines({ row: row, column: column }, lines);
+    };
+    this.insertMergedLines = function (position, lines) {
+        var start = this.clippedPos(position.row, position.column);
+        var end = {
+            row: start.row + lines.length - 1,
+            column: (lines.length == 1 ? start.column : 0) + 
lines[lines.length - 1].length
+        };
+        this.applyDelta({
+            start: start,
+            end: end,
+            action: "insert",
+            lines: lines
+        });
+        return this.clonePos(end);
+    };
+    this.remove = function (range) {
+        var start = this.clippedPos(range.start.row, range.start.column);
+        var end = this.clippedPos(range.end.row, range.end.column);
+        this.applyDelta({
+            start: start,
+            end: end,
+            action: "remove",
+            lines: this.getLinesForRange({ start: start, end: end })
+        });
+        return this.clonePos(start);
+    };
+    this.removeInLine = function (row, startColumn, endColumn) {
+        var start = this.clippedPos(row, startColumn);
+        var end = this.clippedPos(row, endColumn);
+        this.applyDelta({
+            start: start,
+            end: end,
+            action: "remove",
+            lines: this.getLinesForRange({ start: start, end: end })
+        }, true);
+        return this.clonePos(start);
+    };
+    this.removeFullLines = function (firstRow, lastRow) {
+        firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1);
+        lastRow = Math.min(Math.max(0, lastRow), this.getLength() - 1);
+        var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 
0;
+        var deleteLastNewLine = lastRow < this.getLength() - 1;
+        var startRow = (deleteFirstNewLine ? firstRow - 1 : firstRow);
+        var startCol = (deleteFirstNewLine ? this.getLine(startRow).length : 
0);
+        var endRow = (deleteLastNewLine ? lastRow + 1 : lastRow);
+        var endCol = (deleteLastNewLine ? 0 : this.getLine(endRow).length);
+        var range = new Range(startRow, startCol, endRow, endCol);
+        var deletedLines = this.$lines.slice(firstRow, lastRow + 1);
+        this.applyDelta({
+            start: range.start,
+            end: range.end,
+            action: "remove",
+            lines: this.getLinesForRange(range)
+        });
+        return deletedLines;
+    };
+    this.removeNewLine = function (row) {
+        if (row < this.getLength() - 1 && row >= 0) {
+            this.applyDelta({
+                start: this.pos(row, this.getLine(row).length),
+                end: this.pos(row + 1, 0),
+                action: "remove",
+                lines: ["", ""]
+            });
+        }
+    };
+    this.replace = function (range, text) {
+        if (!(range instanceof Range))
+            range = Range.fromPoints(range.start, range.end);
+        if (text.length === 0 && range.isEmpty())
+            return range.start;
+        if (text == this.getTextRange(range))
+            return range.end;
+        this.remove(range);
+        var end;
+        if (text) {
+            end = this.insert(range.start, text);
+        }
+        else {
+            end = range.start;
+        }
+        return end;
+    };
+    this.applyDeltas = function (deltas) {
+        for (var i = 0; i < deltas.length; i++) {
+            this.applyDelta(deltas[i]);
+        }
+    };
+    this.revertDeltas = function (deltas) {
+        for (var i = deltas.length - 1; i >= 0; i--) {
+            this.revertDelta(deltas[i]);
+        }
+    };
+    this.applyDelta = function (delta, doNotValidate) {
+        var isInsert = delta.action == "insert";
+        if (isInsert ? delta.lines.length <= 1 && !delta.lines[0]
+            : !Range.comparePoints(delta.start, delta.end)) {
+            return;
+        }
+        if (isInsert && delta.lines.length > 20000) {
+            this.$splitAndapplyLargeDelta(delta, 20000);
+        }
+        else {
+            applyDelta(this.$lines, delta, doNotValidate);
+            this._signal("change", delta);
+        }
+    };
+    this.$safeApplyDelta = function (delta) {
+        var docLength = this.$lines.length;
+        if (delta.action == "remove" && delta.start.row < docLength && 
delta.end.row < docLength
+            || delta.action == "insert" && delta.start.row <= docLength) {
+            this.applyDelta(delta);
+        }
+    };
+    this.$splitAndapplyLargeDelta = function (delta, MAX) {
+        var lines = delta.lines;
+        var l = lines.length - MAX + 1;
+        var row = delta.start.row;
+        var column = delta.start.column;
+        for (var from = 0, to = 0; from < l; from = to) {
+            to += MAX - 1;
+            var chunk = lines.slice(from, to);
+            chunk.push("");
+            this.applyDelta({
+                start: this.pos(row + from, column),
+                end: this.pos(row + to, column = 0),
+                action: delta.action,
+                lines: chunk
+            }, true);
+        }
+        delta.lines = lines.slice(from);
+        delta.start.row = row + from;
+        delta.start.column = column;
+        this.applyDelta(delta, true);
+    };
+    this.revertDelta = function (delta) {
+        this.$safeApplyDelta({
+            start: this.clonePos(delta.start),
+            end: this.clonePos(delta.end),
+            action: (delta.action == "insert" ? "remove" : "insert"),
+            lines: delta.lines.slice()
+        });
+    };
+    this.indexToPosition = function (index, startRow) {
+        var lines = this.$lines || this.getAllLines();
+        var newlineLength = this.getNewLineCharacter().length;
+        for (var i = startRow || 0, l = lines.length; i < l; i++) {
+            index -= lines[i].length + newlineLength;
+            if (index < 0)
+                return { row: i, column: index + lines[i].length + 
newlineLength };
+        }
+        return { row: l - 1, column: index + lines[l - 1].length + 
newlineLength };
+    };
+    this.positionToIndex = function (pos, startRow) {
+        var lines = this.$lines || this.getAllLines();
+        var newlineLength = this.getNewLineCharacter().length;
+        var index = 0;
+        var row = Math.min(pos.row, lines.length);
+        for (var i = startRow || 0; i < row; ++i)
+            index += lines[i].length + newlineLength;
+        return index + pos.column;
+    };
+}).call(Document.prototype);
+exports.Document = Document;
+
+});
+
+ace.define("ace/lib/lang",[], function(require, exports, module){"use strict";
+exports.last = function (a) {
+    return a[a.length - 1];
+};
+exports.stringReverse = function (string) {
+    return string.split("").reverse().join("");
+};
+exports.stringRepeat = function (string, count) {
+    var result = '';
+    while (count > 0) {
+        if (count & 1)
+            result += string;
+        if (count >>= 1)
+            string += string;
+    }
+    return result;
+};
+var trimBeginRegexp = /^\s\s*/;
+var trimEndRegexp = /\s\s*$/;
+exports.stringTrimLeft = function (string) {
+    return string.replace(trimBeginRegexp, '');
+};
+exports.stringTrimRight = function (string) {
+    return string.replace(trimEndRegexp, '');
+};
+exports.copyObject = function (obj) {
+    var copy = {};
+    for (var key in obj) {
+        copy[key] = obj[key];
+    }
+    return copy;
+};
+exports.copyArray = function (array) {
+    var copy = [];
+    for (var i = 0, l = array.length; i < l; i++) {
+        if (array[i] && typeof array[i] == "object")
+            copy[i] = this.copyObject(array[i]);
+        else
+            copy[i] = array[i];
+    }
+    return copy;
+};
+exports.deepCopy = function deepCopy(obj) {
+    if (typeof obj !== "object" || !obj)
+        return obj;
+    var copy;
+    if (Array.isArray(obj)) {
+        copy = [];
+        for (var key = 0; key < obj.length; key++) {
+            copy[key] = deepCopy(obj[key]);
+        }
+        return copy;
+    }
+    if (Object.prototype.toString.call(obj) !== "[object Object]")
+        return obj;
+    copy = {};
+    for (var key in obj)
+        copy[key] = deepCopy(obj[key]);
+    return copy;
+};
+exports.arrayToMap = function (arr) {
+    var map = {};
+    for (var i = 0; i < arr.length; i++) {
+        map[arr[i]] = 1;
+    }
+    return map;
+};
+exports.createMap = function (props) {
+    var map = Object.create(null);
+    for (var i in props) {
+        map[i] = props[i];
+    }
+    return map;
+};
+exports.arrayRemove = function (array, value) {
+    for (var i = 0; i <= array.length; i++) {
+        if (value === array[i]) {
+            array.splice(i, 1);
+        }
+    }
+};
+exports.escapeRegExp = function (str) {
+    return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
+};
+exports.escapeHTML = function (str) {
+    return ("" + str).replace(/&/g, "&#38;").replace(/"/g, 
"&#34;").replace(/'/g, "&#39;").replace(/</g, "&#60;");
+};
+exports.getMatchOffsets = function (string, regExp) {
+    var matches = [];
+    string.replace(regExp, function (str) {
+        matches.push({
+            offset: arguments[arguments.length - 2],
+            length: str.length
+        });
+    });
+    return matches;
+};
+exports.deferredCall = function (fcn) {
+    var timer = null;
+    var callback = function () {
+        timer = null;
+        fcn();
+    };
+    var deferred = function (timeout) {
+        deferred.cancel();
+        timer = setTimeout(callback, timeout || 0);
+        return deferred;
+    };
+    deferred.schedule = deferred;
+    deferred.call = function () {
+        this.cancel();
+        fcn();
+        return deferred;
+    };
+    deferred.cancel = function () {
+        clearTimeout(timer);
+        timer = null;
+        return deferred;
+    };
+    deferred.isPending = function () {
+        return timer;
+    };
+    return deferred;
+};
+exports.delayedCall = function (fcn, defaultTimeout) {
+    var timer = null;
+    var callback = function () {
+        timer = null;
+        fcn();
+    };
+    var _self = function (timeout) {
+        if (timer == null)
+            timer = setTimeout(callback, timeout || defaultTimeout);
+    };
+    _self.delay = function (timeout) {
+        timer && clearTimeout(timer);
+        timer = setTimeout(callback, timeout || defaultTimeout);
+    };
+    _self.schedule = _self;
+    _self.call = function () {
+        this.cancel();
+        fcn();
+    };
+    _self.cancel = function () {
+        timer && clearTimeout(timer);
+        timer = null;
+    };
+    _self.isPending = function () {
+        return timer;
+    };
+    return _self;
+};
+
+});
+
+ace.define("ace/worker/mirror",[], function(require, exports, module) {
+"use strict";
+
+var Document = require("../document").Document;
+var lang = require("../lib/lang");
+    
+var Mirror = exports.Mirror = function(sender) {
+    this.sender = sender;
+    var doc = this.doc = new Document("");
+    
+    var deferredUpdate = this.deferredUpdate = 
lang.delayedCall(this.onUpdate.bind(this));
+    
+    var _self = this;
+    sender.on("change", function(e) {
+        var data = e.data;
+        if (data[0].start) {
+            doc.applyDeltas(data);
+        } else {
+            for (var i = 0; i < data.length; i += 2) {
+                var d, err; 
+                if (Array.isArray(data[i+1])) {
+                    d = {action: "insert", start: data[i], lines: data[i+1]};
+                } else {
+                    d = {action: "remove", start: data[i], end: data[i+1]};
+                }
+                
+                if ((d.action == "insert" ? d.start : d.end).row >= 
doc.$lines.length) {
+                    err = new Error("Invalid delta");
+                    err.data = {
+                        path: _self.$path,
+                        linesLength: doc.$lines.length,
+                        start: d.start,
+                        end: d.end
+                    };
+                    throw err;
+                }
+
+                doc.applyDelta(d, true);
+            }
+        }
+        if (_self.$timeout)
+            return deferredUpdate.schedule(_self.$timeout);
+        _self.onUpdate();
+    });
+};
+
+(function() {
+    
+    this.$timeout = 500;
+    
+    this.setTimeout = function(timeout) {
+        this.$timeout = timeout;
+    };
+    
+    this.setValue = function(value) {
+        this.doc.setValue(value);
+        this.deferredUpdate.schedule(this.$timeout);
+    };
+    
+    this.getValue = function(callbackId) {
+        this.sender.callback(this.doc.getValue(), callbackId);
+    };
+    
+    this.onUpdate = function() {
+    };
+    
+    this.isPending = function() {
+        return this.deferredUpdate.isPending();
+    };
+    
+}).call(Mirror.prototype);
+
+});
+
+ace.define("ace/mode/mango/mango_parse",[], function(require, exports, module) 
{
+"use strict";
+
+    var at,     // The index of the current character
+        ch,     // The current character
+        escapee = {
+            '"':  '"',
+            '\\': '\\',
+            '/':  '/',
+            b:    '\b',
+            f:    '\f',
+            n:    '\n',
+            r:    '\r',
+            t:    '\t'
+        },
+        text,
+
+        error = function (m) {
+
+            throw {
+                name:    'SyntaxError',
+                message: m,
+                at:      at,
+                text:    text
+            };
+        },
+
+        warnings, // list of warnings
+        addWarning = function (m) {
+            warnings.push(
+                {
+                    name:    'MangoQueryWarning',
+                    message: m,
+                    at:      at,
+                    text:    text
+                }
+            );
+        },
+
+        next = function (c) {
+
+            if (c && c !== ch) {
+                error("Expected '" + c + "' instead of '" + ch + "'");
+            }
+
+            ch = text.charAt(at);
+            at += 1;
+            return ch;
+        },
+
+        number = function () {
+
+            var number,
+                string = '';
+
+            if (ch === '-') {
+                string = '-';
+                next('-');
+            }
+            while (ch >= '0' && ch <= '9') {
+                string += ch;
+                next();
+            }
+            if (ch === '.') {
+                string += '.';
+                while (next() && ch >= '0' && ch <= '9') {
+                    string += ch;
+                }
+            }
+            if (ch === 'e' || ch === 'E') {
+                string += ch;
+                next();
+                if (ch === '-' || ch === '+') {
+                    string += ch;
+                    next();
+                }
+                while (ch >= '0' && ch <= '9') {
+                    string += ch;
+                    next();
+                }
+            }
+            number = +string;
+            if (isNaN(number)) {
+                error("Bad number");
+            } else {
+                return number;
+            }
+        },
+
+        string = function () {
+
+            var hex,
+                i,
+                string = '',
+                uffff;
+
+            if (ch === '"') {
+                while (next()) {
+                    if (ch === '"') {
+                        next();
+                        return string;
+                    } else if (ch === '\\') {
+                        next();
+                        if (ch === 'u') {
+                            uffff = 0;
+                            for (i = 0; i < 4; i += 1) {
+                                hex = parseInt(next(), 16);
+                                if (!isFinite(hex)) {
+                                    break;
+                                }
+                                uffff = uffff * 16 + hex;
+                            }
+                            string += String.fromCharCode(uffff);
+                        } else if (typeof escapee[ch] === 'string') {
+                            string += escapee[ch];
+                        } else {
+                            break;
+                        }
+                    } else if (ch == "\n" || ch == "\r") {
+                        break;
+                    } else {
+                        string += ch;
+                    }
+                }
+            }
+            error("Bad string");
+        },
+
+        white = function () {
+
+            while (ch && ch <= ' ') {
+                next();
+            }
+        },
+
+        word = function () {
+
+            switch (ch) {
+            case 't':
+                next('t');
+                next('r');
+                next('u');
+                next('e');
+                return true;
+            case 'f':
+                next('f');
+                next('a');
+                next('l');
+                next('s');
+                next('e');
+                return false;
+            case 'n':
+                next('n');
+                next('u');
+                next('l');
+                next('l');
+                return null;
+            }
+            error("Unexpected '" + ch + "'");
+        },
+
+        value,  // Place holder for the value function.
+
+        array = function () {
+
+            var array = [];
+
+            if (ch === '[') {
+                next('[');
+                white();
+                if (ch === ']') {
+                    next(']');
+                    return array;   // empty array
+                }
+                while (ch) {
+                    array.push(value());
+                    white();
+                    if (ch === ']') {
+                        next(']');
+                        return array;
+                    }
+                    next(',');
+                    white();
+                }
+            }
+            error("Bad array");
+        },
+
+        object = function () {
+
+            var key,
+                object = {};
+
+            if (ch === '{') {
+                next('{');
+                white();
+                if (ch === '}') {
+                    next('}');
+                    return object;   // empty object
+                }
+                while (ch) {
+                    key = string();
+                    if (key === "$regex") {
+                        addWarning(`${key} operator might prevent optimal use 
of indexes`);
+                    }
+                    else if (key === "$ne" || key === "$or") {
+                        addWarning(`${key} operator requires a full index scan 
when using json indexes`);
+                    }
+                    white();
+                    next(':');
+                    if (Object.hasOwnProperty.call(object, key)) {
+                        error('Duplicate key "' + key + '"');
+                    }
+                    object[key] = value();
+                    white();
+                    if (ch === '}') {
+                        next('}');
+                        return object;
+                    }
+                    next(',');
+                    white();
+                }
+            }
+            error("Bad object");
+        };
+
+    value = function () {
+
+        white();
+        switch (ch) {
+        case '{':
+            return object();
+        case '[':
+            return array();
+        case '"':
+            return string();
+        case '-':
+            return number();
+        default:
+            return ch >= '0' && ch <= '9' ? number() : word();
+        }
+    };
+
+    const mango_parse = function (source, reviver) {
+        var result;
+
+        text = source;
+        at = 0;
+        ch = ' ';
+        warnings = [];
+        mango_parse.warnings = warnings;
+        result = value();
+        white();
+        if (ch) {
+            error("Syntax error");
+        }
+
+        return typeof reviver === 'function' ? function walk(holder, key) {
+            var k, v, value = holder[key];
+            if (value && typeof value === 'object') {
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = walk(value, k);
+                        if (v !== undefined) {
+                            value[k] = v;
+                        } else {
+                            delete value[k];
+                        }
+                    }
+                }
+            }
+            return reviver.call(holder, key, value);
+        }({'': result}, '') : result;
+    };
+    return mango_parse;
+});
+
+ace.define("ace/mode/mango_worker",[], function(require, exports, module) {
+"use strict";
+
+var oop = require("../lib/oop");
+var Mirror = require("../worker/mirror").Mirror;
+var parse = require("./mango/mango_parse");
+
+var MangoWorker = exports.MangoWorker = function(sender) {
+    Mirror.call(this, sender);
+    this.setTimeout(200);
+};
+
+oop.inherits(MangoWorker, Mirror);
+
+(function() {
+
+    this.onUpdate = function() {
+        var value = this.doc.getValue();
+        var errors = [];
+        try {
+            if (value) {
+                parse(value);
+                // console.info('warnings:', parse.warnings);
+            }
+        } catch (e) {
+            var pos = this.doc.indexToPosition(e.at-1);
+            errors.push({
+                row: pos.row,
+                column: pos.column,
+                text: e.message,
+                type: "error"
+            });
+        }
+        const warnings = parse.warnings.map(w => {
+            var pos = this.doc.indexToPosition(w.at-1);
+            return{
+                row: pos.row,
+                column: pos.column,
+                text: w.message,
+                type: "warning"
+            };
+        });
+        errors = errors.concat(warnings);
+        this.sender.emit("annotate", errors);
+    };
+
+}).call(MangoWorker.prototype);
+
+});
diff --git a/app/addons/components/components/codeeditor.js 
b/app/addons/components/components/codeeditor.js
index d6c788ce..126d6c26 100644
--- a/app/addons/components/components/codeeditor.js
+++ b/app/addons/components/components/codeeditor.js
@@ -286,8 +286,8 @@ export class CodeEditor extends React.Component {
   };
 
   hasErrors = () => {
-    return !_.every(this.getAnnotations(), (error) => {
-      return this.isIgnorableError(error.raw);
+    return !_.every(this.getAnnotations(), (annotation) => {
+      return this.isIgnorableError(annotation.raw) || annotation.type !== 
'error';
     });
   };
 
diff --git a/app/addons/documents/assets/scss/view-editor.scss 
b/app/addons/documents/assets/scss/view-editor.scss
index 19d8ebe9..1853dc8b 100644
--- a/app/addons/documents/assets/scss/view-editor.scss
+++ b/app/addons/documents/assets/scss/view-editor.scss
@@ -68,6 +68,9 @@
     & > div {
       height: 90%;
     }
+    & > div.full-page-editor-modal-wrapper {
+      height: 100%;
+    }
   }
 }
 
diff --git a/app/addons/documents/mango/components/MangoQueryEditor.js 
b/app/addons/documents/mango/components/MangoQueryEditor.js
index b5d1ff44..0a9b9981 100644
--- a/app/addons/documents/mango/components/MangoQueryEditor.js
+++ b/app/addons/documents/mango/components/MangoQueryEditor.js
@@ -112,7 +112,7 @@ export default class MangoQueryEditor extends Component {
               ref={node => this.codeEditor = node}
               title={this.props.editorTitle}
               docLink={getDocUrl('MANGO_SEARCH')}
-              syntaxMode="javascript"
+              syntaxMode="mango"
               defaultCode={this.props.queryFindCode}
               showCheatSheetIcon={true}
               onCheatsheatIconClick={this.showCheatsheetModal}
diff --git a/jest-config.json b/jest-config.json
index 35c19652..934b8bf2 100644
--- a/jest-config.json
+++ b/jest-config.json
@@ -8,6 +8,8 @@
   "moduleNameMapper": {
     "underscore": "lodash",
     "ace-builds": "<rootDir>/node_modules/ace-builds",
+    "./ace-mode-mango.js": 
"<rootDir>/app/addons/components/components/ace-mode-mango.js",
+    "./ace-worker-mango.js": 
"<rootDir>/app/addons/components/components/ace-worker-mango.js",
 
     
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|swf|wav|mp3|m4a|aac|oga)$":
 "<rootDir>/__mocks__/fileMock.js",
     "\\.(css|less|scss)$": "<rootDir>/__mocks__/styleMock.js"

Reply via email to