Forráskód Böngészése

Editor: Integrate tern-threejs in script editor #6585

Joshua Koo 10 éve
szülő
commit
56bce462a5

+ 18 - 0
editor/index.html

@@ -47,6 +47,24 @@
 
 
 		<script src="js/libs/codemirror/mode/glsl.js"></script>
 		<script src="js/libs/codemirror/mode/glsl.js"></script>
 
 
+		<link rel="stylesheet" href="js/libs/codemirror/addon/dialog.css">
+		<link rel="stylesheet" href="js/libs/codemirror/addon/show-hint.css">
+		<link rel="stylesheet" href="js/libs/codemirror/addon/tern.css">
+		<script src="js/libs/codemirror/addon/dialog.js"></script>
+		<script src="js/libs/codemirror/addon/show-hint.js"></script>
+		<script src="js/libs/codemirror/addon/tern.js"></script>
+		<script src="js/libs/acorn/acorn.js"></script>
+		<script src="js/libs/acorn/acorn_loose.js"></script>
+		<script src="js/libs/acorn/walk.js"></script>
+		<script src="js/libs/ternjs/polyfill.js"></script>
+		<script src="js/libs/ternjs/signal.js"></script>
+		<script src="js/libs/ternjs/tern.js"></script>
+		<script src="js/libs/ternjs/def.js"></script>
+		<script src="js/libs/ternjs/comment.js"></script>
+		<script src="js/libs/ternjs/infer.js"></script>
+		<script src="js/libs/ternjs/doc_comment.js"></script>
+		<script src="js/libs/tern-threejs/threejs.js"></script>
+
 		<script src="js/libs/jszip.min.js"></script>
 		<script src="js/libs/jszip.min.js"></script>
 		<script src="js/libs/sortable.min.js"></script>
 		<script src="js/libs/sortable.min.js"></script>
 		<script src="js/libs/signals.min.js"></script>
 		<script src="js/libs/signals.min.js"></script>

+ 41 - 2
editor/js/Script.js

@@ -53,7 +53,10 @@ var Script = function ( editor ) {
 		matchBrackets: true,
 		matchBrackets: true,
 		indentWithTabs: true,
 		indentWithTabs: true,
 		tabSize: 4,
 		tabSize: 4,
-		indentUnit: 4
+		indentUnit: 4,
+		hintOptions: {
+			completeSingle: false
+		}
 	} );
 	} );
 	codemirror.setOption( 'theme', 'monokai' );
 	codemirror.setOption( 'theme', 'monokai' );
 	codemirror.on( 'change', function () {
 	codemirror.on( 'change', function () {
@@ -108,7 +111,6 @@ var Script = function ( editor ) {
 
 
 	} );
 	} );
 
 
-
 	// validate
 	// validate
 
 
 	var errorLines = [];
 	var errorLines = [];
@@ -223,6 +225,43 @@ var Script = function ( editor ) {
 
 
 	};
 	};
 
 
+	// tern js autocomplete
+
+	var server = new CodeMirror.TernServer( {
+		caseInsensitive: true,
+		plugins: { threejs: null }
+	} );
+
+	codemirror.setOption( 'extraKeys', {
+		'Ctrl-Space': function(cm) { server.complete(cm); },
+		'Ctrl-I': function(cm) { server.showType(cm); },
+		'Ctrl-O': function(cm) { server.showDocs(cm); },
+		'Alt-.': function(cm) { server.jumpToDef(cm); },
+		'Alt-,': function(cm) { server.jumpBack(cm); },
+		'Ctrl-Q': function(cm) { server.rename(cm); },
+		'Ctrl-.': function(cm) { server.selectName(cm); }
+	} );
+
+	codemirror.on( 'cursorActivity', function( cm ) {
+
+		if ( currentMode !== 'javascript' ) return;
+		server.updateArgHints( cm );
+
+	} );
+
+	codemirror.on( 'keypress', function( cm, kb ) {
+
+		if ( currentMode !== 'javascript' ) return;
+		var typed = String.fromCharCode( kb.which || kb.keyCode );
+		if ( /[\w\.]/.exec( typed ) ) {
+
+			server.complete( cm );
+
+		}
+
+	} );
+
+
 	//
 	//
 
 
 	signals.editorCleared.add( function () {
 	signals.editorCleared.add( function () {

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 877 - 0
editor/js/libs/acorn/acorn.js


+ 1299 - 0
editor/js/libs/acorn/acorn_loose.js

@@ -0,0 +1,1299 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.acorn || (g.acorn = {})).loose = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+"use strict";
+
+var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { "default": obj }; };
+
+exports.parse_dammit = parse_dammit;
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+// Acorn: Loose parser
+//
+// This module provides an alternative parser (`parse_dammit`) that
+// exposes that same interface as `parse`, but will try to parse
+// anything as JavaScript, repairing syntax error the best it can.
+// There are circumstances in which it will raise an error and give
+// up, but they are very rare. The resulting AST will be a mostly
+// valid JavaScript AST (as per the [Mozilla parser API][api], except
+// that:
+//
+// - Return outside functions is allowed
+//
+// - Label consistency (no conflicts, break only to existing labels)
+//   is not enforced.
+//
+// - Bogus Identifier nodes with a name of `"✖"` are inserted whenever
+//   the parser got too confused to return anything meaningful.
+//
+// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
+//
+// The expected use for this is to *first* try `acorn.parse`, and only
+// if that fails switch to `parse_dammit`. The loose parser might
+// parse badly indented code incorrectly, so **don't** use it as
+// your default parser.
+//
+// Quite a lot of acorn.js is duplicated here. The alternative was to
+// add a *lot* of extra cruft to that file, making it less readable
+// and slower. Copying and editing the code allowed me to make
+// invasive changes and simplifications without creating a complicated
+// tangle.
+
+var acorn = _interopRequireWildcard(require(".."));
+
+var _state = require("./state");
+
+var LooseParser = _state.LooseParser;
+
+require("./tokenize");
+
+require("./parseutil");
+
+require("./statement");
+
+require("./expression");
+
+exports.LooseParser = _state.LooseParser;
+
+acorn.defaultOptions.tabSize = 4;
+
+function parse_dammit(input, options) {
+  var p = new LooseParser(input, options);
+  p.next();
+  return p.parseTopLevel();
+}
+
+acorn.parse_dammit = parse_dammit;
+acorn.LooseParser = LooseParser;
+
+},{"..":2,"./expression":3,"./parseutil":4,"./state":5,"./statement":6,"./tokenize":7}],2:[function(require,module,exports){
+"use strict";
+
+module.exports = typeof window != "undefined" ? window.acorn : require(("suppress", "./acorn"));
+
+},{}],3:[function(require,module,exports){
+"use strict";
+
+var LooseParser = require("./state").LooseParser;
+
+var isDummy = require("./parseutil").isDummy;
+
+var tt = require("..").tokTypes;
+
+var lp = LooseParser.prototype;
+
+lp.checkLVal = function (expr) {
+  if (!expr) return expr;
+  switch (expr.type) {
+    case "Identifier":
+    case "MemberExpression":
+    case "ObjectPattern":
+    case "ArrayPattern":
+    case "RestElement":
+    case "AssignmentPattern":
+      return expr;
+
+    default:
+      return this.dummyIdent();
+  }
+};
+
+lp.parseExpression = function (noIn) {
+  var start = this.storeCurrentPos();
+  var expr = this.parseMaybeAssign(noIn);
+  if (this.tok.type === tt.comma) {
+    var node = this.startNodeAt(start);
+    node.expressions = [expr];
+    while (this.eat(tt.comma)) node.expressions.push(this.parseMaybeAssign(noIn));
+    return this.finishNode(node, "SequenceExpression");
+  }
+  return expr;
+};
+
+lp.parseParenExpression = function () {
+  this.pushCx();
+  this.expect(tt.parenL);
+  var val = this.parseExpression();
+  this.popCx();
+  this.expect(tt.parenR);
+  return val;
+};
+
+lp.parseMaybeAssign = function (noIn) {
+  var start = this.storeCurrentPos();
+  var left = this.parseMaybeConditional(noIn);
+  if (this.tok.type.isAssign) {
+    var node = this.startNodeAt(start);
+    node.operator = this.tok.value;
+    node.left = this.tok.type === tt.eq ? this.toAssignable(left) : this.checkLVal(left);
+    this.next();
+    node.right = this.parseMaybeAssign(noIn);
+    return this.finishNode(node, "AssignmentExpression");
+  }
+  return left;
+};
+
+lp.parseMaybeConditional = function (noIn) {
+  var start = this.storeCurrentPos();
+  var expr = this.parseExprOps(noIn);
+  if (this.eat(tt.question)) {
+    var node = this.startNodeAt(start);
+    node.test = expr;
+    node.consequent = this.parseMaybeAssign();
+    node.alternate = this.expect(tt.colon) ? this.parseMaybeAssign(noIn) : this.dummyIdent();
+    return this.finishNode(node, "ConditionalExpression");
+  }
+  return expr;
+};
+
+lp.parseExprOps = function (noIn) {
+  var start = this.storeCurrentPos();
+  var indent = this.curIndent,
+      line = this.curLineStart;
+  return this.parseExprOp(this.parseMaybeUnary(noIn), start, -1, noIn, indent, line);
+};
+
+lp.parseExprOp = function (left, start, minPrec, noIn, indent, line) {
+  if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) return left;
+  var prec = this.tok.type.binop;
+  if (prec != null && (!noIn || this.tok.type !== tt._in)) {
+    if (prec > minPrec) {
+      var node = this.startNodeAt(start);
+      node.left = left;
+      node.operator = this.tok.value;
+      this.next();
+      if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) {
+        node.right = this.dummyIdent();
+      } else {
+        var rightStart = this.storeCurrentPos();
+        node.right = this.parseExprOp(this.parseMaybeUnary(noIn), rightStart, prec, noIn, indent, line);
+      }
+      this.finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
+      return this.parseExprOp(node, start, minPrec, noIn, indent, line);
+    }
+  }
+  return left;
+};
+
+lp.parseMaybeUnary = function (noIn) {
+  if (this.tok.type.prefix) {
+    var node = this.startNode(),
+        update = this.tok.type === tt.incDec;
+    node.operator = this.tok.value;
+    node.prefix = true;
+    this.next();
+    node.argument = this.parseMaybeUnary(noIn);
+    if (update) node.argument = this.checkLVal(node.argument);
+    return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
+  } else if (this.tok.type === tt.ellipsis) {
+    var node = this.startNode();
+    this.next();
+    node.argument = this.parseMaybeUnary(noIn);
+    return this.finishNode(node, "SpreadElement");
+  }
+  var start = this.storeCurrentPos();
+  var expr = this.parseExprSubscripts();
+  while (this.tok.type.postfix && !this.canInsertSemicolon()) {
+    var node = this.startNodeAt(start);
+    node.operator = this.tok.value;
+    node.prefix = false;
+    node.argument = this.checkLVal(expr);
+    this.next();
+    expr = this.finishNode(node, "UpdateExpression");
+  }
+  return expr;
+};
+
+lp.parseExprSubscripts = function () {
+  var start = this.storeCurrentPos();
+  return this.parseSubscripts(this.parseExprAtom(), start, false, this.curIndent, this.curLineStart);
+};
+
+lp.parseSubscripts = function (base, start, noCalls, startIndent, line) {
+  for (;;) {
+    if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) {
+      if (this.tok.type == tt.dot && this.curIndent == startIndent) --startIndent;else return base;
+    }
+
+    if (this.eat(tt.dot)) {
+      var node = this.startNodeAt(start);
+      node.object = base;
+      if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) node.property = this.dummyIdent();else node.property = this.parsePropertyAccessor() || this.dummyIdent();
+      node.computed = false;
+      base = this.finishNode(node, "MemberExpression");
+    } else if (this.tok.type == tt.bracketL) {
+      this.pushCx();
+      this.next();
+      var node = this.startNodeAt(start);
+      node.object = base;
+      node.property = this.parseExpression();
+      node.computed = true;
+      this.popCx();
+      this.expect(tt.bracketR);
+      base = this.finishNode(node, "MemberExpression");
+    } else if (!noCalls && this.tok.type == tt.parenL) {
+      this.pushCx();
+      var node = this.startNodeAt(start);
+      node.callee = base;
+      node.arguments = this.parseExprList(tt.parenR);
+      base = this.finishNode(node, "CallExpression");
+    } else if (this.tok.type == tt.backQuote) {
+      var node = this.startNodeAt(start);
+      node.tag = base;
+      node.quasi = this.parseTemplate();
+      base = this.finishNode(node, "TaggedTemplateExpression");
+    } else {
+      return base;
+    }
+  }
+};
+
+lp.parseExprAtom = function () {
+  var node = undefined;
+  switch (this.tok.type) {
+    case tt._this:
+    case tt._super:
+      var type = this.tok.type === tt._this ? "ThisExpression" : "Super";
+      node = this.startNode();
+      this.next();
+      return this.finishNode(node, type);
+
+    case tt.name:
+      var start = this.storeCurrentPos();
+      var id = this.parseIdent();
+      return this.eat(tt.arrow) ? this.parseArrowExpression(this.startNodeAt(start), [id]) : id;
+
+    case tt.regexp:
+      node = this.startNode();
+      var val = this.tok.value;
+      node.regex = { pattern: val.pattern, flags: val.flags };
+      node.value = val.value;
+      node.raw = this.input.slice(this.tok.start, this.tok.end);
+      this.next();
+      return this.finishNode(node, "Literal");
+
+    case tt.num:case tt.string:
+      node = this.startNode();
+      node.value = this.tok.value;
+      node.raw = this.input.slice(this.tok.start, this.tok.end);
+      this.next();
+      return this.finishNode(node, "Literal");
+
+    case tt._null:case tt._true:case tt._false:
+      node = this.startNode();
+      node.value = this.tok.type === tt._null ? null : this.tok.type === tt._true;
+      node.raw = this.tok.type.keyword;
+      this.next();
+      return this.finishNode(node, "Literal");
+
+    case tt.parenL:
+      var parenStart = this.storeCurrentPos();
+      this.next();
+      var inner = this.parseExpression();
+      this.expect(tt.parenR);
+      if (this.eat(tt.arrow)) {
+        return this.parseArrowExpression(this.startNodeAt(parenStart), inner.expressions || (isDummy(inner) ? [] : [inner]));
+      }
+      if (this.options.preserveParens) {
+        var par = this.startNodeAt(parenStart);
+        par.expression = inner;
+        inner = this.finishNode(par, "ParenthesizedExpression");
+      }
+      return inner;
+
+    case tt.bracketL:
+      node = this.startNode();
+      this.pushCx();
+      node.elements = this.parseExprList(tt.bracketR, true);
+      return this.finishNode(node, "ArrayExpression");
+
+    case tt.braceL:
+      return this.parseObj();
+
+    case tt._class:
+      return this.parseClass();
+
+    case tt._function:
+      node = this.startNode();
+      this.next();
+      return this.parseFunction(node, false);
+
+    case tt._new:
+      return this.parseNew();
+
+    case tt._yield:
+      node = this.startNode();
+      this.next();
+      if (this.semicolon() || this.canInsertSemicolon() || this.tok.type != tt.star && !this.tok.type.startsExpr) {
+        node.delegate = false;
+        node.argument = null;
+      } else {
+        node.delegate = this.eat(tt.star);
+        node.argument = this.parseMaybeAssign();
+      }
+      return this.finishNode(node, "YieldExpression");
+
+    case tt.backQuote:
+      return this.parseTemplate();
+
+    default:
+      return this.dummyIdent();
+  }
+};
+
+lp.parseNew = function () {
+  var node = this.startNode(),
+      startIndent = this.curIndent,
+      line = this.curLineStart;
+  var meta = this.parseIdent(true);
+  if (this.options.ecmaVersion >= 6 && this.eat(tt.dot)) {
+    node.meta = meta;
+    node.property = this.parseIdent(true);
+    return this.finishNode(node, "MetaProperty");
+  }
+  var start = this.storeCurrentPos();
+  node.callee = this.parseSubscripts(this.parseExprAtom(), start, true, startIndent, line);
+  if (this.tok.type == tt.parenL) {
+    this.pushCx();
+    node.arguments = this.parseExprList(tt.parenR);
+  } else {
+    node.arguments = [];
+  }
+  return this.finishNode(node, "NewExpression");
+};
+
+lp.parseTemplateElement = function () {
+  var elem = this.startNode();
+  elem.value = {
+    raw: this.input.slice(this.tok.start, this.tok.end),
+    cooked: this.tok.value
+  };
+  this.next();
+  elem.tail = this.tok.type === tt.backQuote;
+  return this.finishNode(elem, "TemplateElement");
+};
+
+lp.parseTemplate = function () {
+  var node = this.startNode();
+  this.next();
+  node.expressions = [];
+  var curElt = this.parseTemplateElement();
+  node.quasis = [curElt];
+  while (!curElt.tail) {
+    this.next();
+    node.expressions.push(this.parseExpression());
+    if (this.expect(tt.braceR)) {
+      curElt = this.parseTemplateElement();
+    } else {
+      curElt = this.startNode();
+      curElt.value = { cooked: "", raw: "" };
+      curElt.tail = true;
+    }
+    node.quasis.push(curElt);
+  }
+  this.expect(tt.backQuote);
+  return this.finishNode(node, "TemplateLiteral");
+};
+
+lp.parseObj = function () {
+  var node = this.startNode();
+  node.properties = [];
+  this.pushCx();
+  var indent = this.curIndent + 1,
+      line = this.curLineStart;
+  this.eat(tt.braceL);
+  if (this.curIndent + 1 < indent) {
+    indent = this.curIndent;line = this.curLineStart;
+  }
+  while (!this.closes(tt.braceR, indent, line)) {
+    var prop = this.startNode(),
+        isGenerator = undefined,
+        start = undefined;
+    if (this.options.ecmaVersion >= 6) {
+      start = this.storeCurrentPos();
+      prop.method = false;
+      prop.shorthand = false;
+      isGenerator = this.eat(tt.star);
+    }
+    this.parsePropertyName(prop);
+    if (isDummy(prop.key)) {
+      if (isDummy(this.parseMaybeAssign())) this.next();this.eat(tt.comma);continue;
+    }
+    if (this.eat(tt.colon)) {
+      prop.kind = "init";
+      prop.value = this.parseMaybeAssign();
+    } else if (this.options.ecmaVersion >= 6 && (this.tok.type === tt.parenL || this.tok.type === tt.braceL)) {
+      prop.kind = "init";
+      prop.method = true;
+      prop.value = this.parseMethod(isGenerator);
+    } else if (this.options.ecmaVersion >= 5 && prop.key.type === "Identifier" && !prop.computed && (prop.key.name === "get" || prop.key.name === "set") && (this.tok.type != tt.comma && this.tok.type != tt.braceR)) {
+      prop.kind = prop.key.name;
+      this.parsePropertyName(prop);
+      prop.value = this.parseMethod(false);
+    } else {
+      prop.kind = "init";
+      if (this.options.ecmaVersion >= 6) {
+        if (this.eat(tt.eq)) {
+          var assign = this.startNodeAt(start);
+          assign.operator = "=";
+          assign.left = prop.key;
+          assign.right = this.parseMaybeAssign();
+          prop.value = this.finishNode(assign, "AssignmentExpression");
+        } else {
+          prop.value = prop.key;
+        }
+      } else {
+        prop.value = this.dummyIdent();
+      }
+      prop.shorthand = true;
+    }
+    node.properties.push(this.finishNode(prop, "Property"));
+    this.eat(tt.comma);
+  }
+  this.popCx();
+  if (!this.eat(tt.braceR)) {
+    // If there is no closing brace, make the node span to the start
+    // of the next token (this is useful for Tern)
+    this.last.end = this.tok.start;
+    if (this.options.locations) this.last.loc.end = this.tok.loc.start;
+  }
+  return this.finishNode(node, "ObjectExpression");
+};
+
+lp.parsePropertyName = function (prop) {
+  if (this.options.ecmaVersion >= 6) {
+    if (this.eat(tt.bracketL)) {
+      prop.computed = true;
+      prop.key = this.parseExpression();
+      this.expect(tt.bracketR);
+      return;
+    } else {
+      prop.computed = false;
+    }
+  }
+  var key = this.tok.type === tt.num || this.tok.type === tt.string ? this.parseExprAtom() : this.parseIdent();
+  prop.key = key || this.dummyIdent();
+};
+
+lp.parsePropertyAccessor = function () {
+  if (this.tok.type === tt.name || this.tok.type.keyword) return this.parseIdent();
+};
+
+lp.parseIdent = function () {
+  var name = this.tok.type === tt.name ? this.tok.value : this.tok.type.keyword;
+  if (!name) return this.dummyIdent();
+  var node = this.startNode();
+  this.next();
+  node.name = name;
+  return this.finishNode(node, "Identifier");
+};
+
+lp.initFunction = function (node) {
+  node.id = null;
+  node.params = [];
+  if (this.options.ecmaVersion >= 6) {
+    node.generator = false;
+    node.expression = false;
+  }
+};
+
+// Convert existing expression atom to assignable pattern
+// if possible.
+
+lp.toAssignable = function (node) {
+  if (this.options.ecmaVersion >= 6 && node) {
+    switch (node.type) {
+      case "ObjectExpression":
+        node.type = "ObjectPattern";
+        var props = node.properties;
+        for (var i = 0; i < props.length; i++) {
+          this.toAssignable(props[i].value);
+        }break;
+
+      case "ArrayExpression":
+        node.type = "ArrayPattern";
+        this.toAssignableList(node.elements);
+        break;
+
+      case "SpreadElement":
+        node.type = "RestElement";
+        node.argument = this.toAssignable(node.argument);
+        break;
+
+      case "AssignmentExpression":
+        node.type = "AssignmentPattern";
+        break;
+    }
+  }
+  return this.checkLVal(node);
+};
+
+lp.toAssignableList = function (exprList) {
+  for (var i = 0; i < exprList.length; i++) {
+    this.toAssignable(exprList[i]);
+  }return exprList;
+};
+
+lp.parseFunctionParams = function (params) {
+  this.pushCx();
+  params = this.parseExprList(tt.parenR);
+  return this.toAssignableList(params);
+};
+
+lp.parseMethod = function (isGenerator) {
+  var node = this.startNode();
+  this.initFunction(node);
+  node.params = this.parseFunctionParams();
+  node.generator = isGenerator || false;
+  node.expression = this.options.ecmaVersion >= 6 && this.tok.type !== tt.braceL;
+  node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock();
+  return this.finishNode(node, "FunctionExpression");
+};
+
+lp.parseArrowExpression = function (node, params) {
+  this.initFunction(node);
+  node.params = this.toAssignableList(params);
+  node.expression = this.tok.type !== tt.braceL;
+  node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock();
+  return this.finishNode(node, "ArrowFunctionExpression");
+};
+
+lp.parseExprList = function (close, allowEmpty) {
+  var indent = this.curIndent,
+      line = this.curLineStart,
+      elts = [];
+  this.next(); // Opening bracket
+  while (!this.closes(close, indent + 1, line)) {
+    if (this.eat(tt.comma)) {
+      elts.push(allowEmpty ? null : this.dummyIdent());
+      continue;
+    }
+    var elt = this.parseMaybeAssign();
+    if (isDummy(elt)) {
+      if (this.closes(close, indent, line)) break;
+      this.next();
+    } else {
+      elts.push(elt);
+    }
+    this.eat(tt.comma);
+  }
+  this.popCx();
+  if (!this.eat(close)) {
+    // If there is no closing brace, make the node span to the start
+    // of the next token (this is useful for Tern)
+    this.last.end = this.tok.start;
+    if (this.options.locations) this.last.loc.end = this.tok.loc.start;
+  }
+  return elts;
+};
+
+},{"..":2,"./parseutil":4,"./state":5}],4:[function(require,module,exports){
+"use strict";
+
+exports.isDummy = isDummy;
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var LooseParser = require("./state").LooseParser;
+
+var _ = require("..");
+
+var Node = _.Node;
+var SourceLocation = _.SourceLocation;
+var lineBreak = _.lineBreak;
+var isNewLine = _.isNewLine;
+var tt = _.tokTypes;
+
+var lp = LooseParser.prototype;
+
+lp.startNode = function () {
+  var node = new Node();
+  node.start = this.tok.start;
+  if (this.options.locations) node.loc = new SourceLocation(this.toks, this.tok.loc.start);
+  if (this.options.directSourceFile) node.sourceFile = this.options.directSourceFile;
+  if (this.options.ranges) node.range = [this.tok.start, 0];
+  return node;
+};
+
+lp.storeCurrentPos = function () {
+  return this.options.locations ? [this.tok.start, this.tok.loc.start] : this.tok.start;
+};
+
+lp.startNodeAt = function (pos) {
+  var node = new Node();
+  if (this.options.locations) {
+    node.start = pos[0];
+    node.loc = new SourceLocation(this.toks, pos[1]);
+    pos = pos[0];
+  } else {
+    node.start = pos;
+  }
+  if (this.options.directSourceFile) node.sourceFile = this.options.directSourceFile;
+  if (this.options.ranges) node.range = [pos, 0];
+  return node;
+};
+
+lp.finishNode = function (node, type) {
+  node.type = type;
+  node.end = this.last.end;
+  if (this.options.locations) node.loc.end = this.last.loc.end;
+  if (this.options.ranges) node.range[1] = this.last.end;
+  return node;
+};
+
+lp.dummyIdent = function () {
+  var dummy = this.startNode();
+  dummy.name = "✖";
+  return this.finishNode(dummy, "Identifier");
+};
+
+function isDummy(node) {
+  return node.name == "✖";
+}
+
+lp.eat = function (type) {
+  if (this.tok.type === type) {
+    this.next();
+    return true;
+  } else {
+    return false;
+  }
+};
+
+lp.isContextual = function (name) {
+  return this.tok.type === tt.name && this.tok.value === name;
+};
+
+lp.eatContextual = function (name) {
+  return this.tok.value === name && this.eat(tt.name);
+};
+
+lp.canInsertSemicolon = function () {
+  return this.tok.type === tt.eof || this.tok.type === tt.braceR || lineBreak.test(this.input.slice(this.last.end, this.tok.start));
+};
+
+lp.semicolon = function () {
+  return this.eat(tt.semi);
+};
+
+lp.expect = function (type) {
+  if (this.eat(type)) return true;
+  for (var i = 1; i <= 2; i++) {
+    if (this.lookAhead(i).type == type) {
+      for (var j = 0; j < i; j++) {
+        this.next();
+      }return true;
+    }
+  }
+};
+
+lp.pushCx = function () {
+  this.context.push(this.curIndent);
+};
+lp.popCx = function () {
+  this.curIndent = this.context.pop();
+};
+
+lp.lineEnd = function (pos) {
+  while (pos < this.input.length && !isNewLine(this.input.charCodeAt(pos))) ++pos;
+  return pos;
+};
+
+lp.indentationAfter = function (pos) {
+  for (var count = 0;; ++pos) {
+    var ch = this.input.charCodeAt(pos);
+    if (ch === 32) ++count;else if (ch === 9) count += this.options.tabSize;else return count;
+  }
+};
+
+lp.closes = function (closeTok, indent, line, blockHeuristic) {
+  if (this.tok.type === closeTok || this.tok.type === tt.eof) return true;
+  return line != this.curLineStart && this.curIndent < indent && this.tokenStartsLine() && (!blockHeuristic || this.nextLineStart >= this.input.length || this.indentationAfter(this.nextLineStart) < indent);
+};
+
+lp.tokenStartsLine = function () {
+  for (var p = this.tok.start - 1; p >= this.curLineStart; --p) {
+    var ch = this.input.charCodeAt(p);
+    if (ch !== 9 && ch !== 32) return false;
+  }
+  return true;
+};
+
+},{"..":2,"./state":5}],5:[function(require,module,exports){
+"use strict";
+
+exports.LooseParser = LooseParser;
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _ = require("..");
+
+var tokenizer = _.tokenizer;
+var SourceLocation = _.SourceLocation;
+var tt = _.tokTypes;
+
+function LooseParser(input, options) {
+  this.toks = tokenizer(input, options);
+  this.options = this.toks.options;
+  this.input = this.toks.input;
+  this.tok = this.last = { type: tt.eof, start: 0, end: 0 };
+  if (this.options.locations) {
+    var here = this.toks.curPosition();
+    this.tok.loc = new SourceLocation(this.toks, here, here);
+  }
+  this.ahead = []; // Tokens ahead
+  this.context = []; // Indentation contexted
+  this.curIndent = 0;
+  this.curLineStart = 0;
+  this.nextLineStart = this.lineEnd(this.curLineStart) + 1;
+}
+
+},{"..":2}],6:[function(require,module,exports){
+"use strict";
+
+var LooseParser = require("./state").LooseParser;
+
+var isDummy = require("./parseutil").isDummy;
+
+var _ = require("..");
+
+var getLineInfo = _.getLineInfo;
+var tt = _.tokTypes;
+
+var lp = LooseParser.prototype;
+
+lp.parseTopLevel = function () {
+  var node = this.startNodeAt(this.options.locations ? [0, getLineInfo(this.input, 0)] : 0);
+  node.body = [];
+  while (this.tok.type !== tt.eof) node.body.push(this.parseStatement());
+  this.last = this.tok;
+  if (this.options.ecmaVersion >= 6) {
+    node.sourceType = this.options.sourceType;
+  }
+  return this.finishNode(node, "Program");
+};
+
+lp.parseStatement = function () {
+  var starttype = this.tok.type,
+      node = this.startNode();
+
+  switch (starttype) {
+    case tt._break:case tt._continue:
+      this.next();
+      var isBreak = starttype === tt._break;
+      if (this.semicolon() || this.canInsertSemicolon()) {
+        node.label = null;
+      } else {
+        node.label = this.tok.type === tt.name ? this.parseIdent() : null;
+        this.semicolon();
+      }
+      return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+
+    case tt._debugger:
+      this.next();
+      this.semicolon();
+      return this.finishNode(node, "DebuggerStatement");
+
+    case tt._do:
+      this.next();
+      node.body = this.parseStatement();
+      node.test = this.eat(tt._while) ? this.parseParenExpression() : this.dummyIdent();
+      this.semicolon();
+      return this.finishNode(node, "DoWhileStatement");
+
+    case tt._for:
+      this.next();
+      this.pushCx();
+      this.expect(tt.parenL);
+      if (this.tok.type === tt.semi) return this.parseFor(node, null);
+      if (this.tok.type === tt._var || this.tok.type === tt._let || this.tok.type === tt._const) {
+        var _init = this.parseVar(true);
+        if (_init.declarations.length === 1 && (this.tok.type === tt._in || this.isContextual("of"))) {
+          return this.parseForIn(node, _init);
+        }
+        return this.parseFor(node, _init);
+      }
+      var init = this.parseExpression(true);
+      if (this.tok.type === tt._in || this.isContextual("of")) return this.parseForIn(node, this.toAssignable(init));
+      return this.parseFor(node, init);
+
+    case tt._function:
+      this.next();
+      return this.parseFunction(node, true);
+
+    case tt._if:
+      this.next();
+      node.test = this.parseParenExpression();
+      node.consequent = this.parseStatement();
+      node.alternate = this.eat(tt._else) ? this.parseStatement() : null;
+      return this.finishNode(node, "IfStatement");
+
+    case tt._return:
+      this.next();
+      if (this.eat(tt.semi) || this.canInsertSemicolon()) node.argument = null;else {
+        node.argument = this.parseExpression();this.semicolon();
+      }
+      return this.finishNode(node, "ReturnStatement");
+
+    case tt._switch:
+      var blockIndent = this.curIndent,
+          line = this.curLineStart;
+      this.next();
+      node.discriminant = this.parseParenExpression();
+      node.cases = [];
+      this.pushCx();
+      this.expect(tt.braceL);
+
+      var cur = undefined;
+      while (!this.closes(tt.braceR, blockIndent, line, true)) {
+        if (this.tok.type === tt._case || this.tok.type === tt._default) {
+          var isCase = this.tok.type === tt._case;
+          if (cur) this.finishNode(cur, "SwitchCase");
+          node.cases.push(cur = this.startNode());
+          cur.consequent = [];
+          this.next();
+          if (isCase) cur.test = this.parseExpression();else cur.test = null;
+          this.expect(tt.colon);
+        } else {
+          if (!cur) {
+            node.cases.push(cur = this.startNode());
+            cur.consequent = [];
+            cur.test = null;
+          }
+          cur.consequent.push(this.parseStatement());
+        }
+      }
+      if (cur) this.finishNode(cur, "SwitchCase");
+      this.popCx();
+      this.eat(tt.braceR);
+      return this.finishNode(node, "SwitchStatement");
+
+    case tt._throw:
+      this.next();
+      node.argument = this.parseExpression();
+      this.semicolon();
+      return this.finishNode(node, "ThrowStatement");
+
+    case tt._try:
+      this.next();
+      node.block = this.parseBlock();
+      node.handler = null;
+      if (this.tok.type === tt._catch) {
+        var clause = this.startNode();
+        this.next();
+        this.expect(tt.parenL);
+        clause.param = this.toAssignable(this.parseExprAtom());
+        this.expect(tt.parenR);
+        clause.guard = null;
+        clause.body = this.parseBlock();
+        node.handler = this.finishNode(clause, "CatchClause");
+      }
+      node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null;
+      if (!node.handler && !node.finalizer) return node.block;
+      return this.finishNode(node, "TryStatement");
+
+    case tt._var:
+    case tt._let:
+    case tt._const:
+      return this.parseVar();
+
+    case tt._while:
+      this.next();
+      node.test = this.parseParenExpression();
+      node.body = this.parseStatement();
+      return this.finishNode(node, "WhileStatement");
+
+    case tt._with:
+      this.next();
+      node.object = this.parseParenExpression();
+      node.body = this.parseStatement();
+      return this.finishNode(node, "WithStatement");
+
+    case tt.braceL:
+      return this.parseBlock();
+
+    case tt.semi:
+      this.next();
+      return this.finishNode(node, "EmptyStatement");
+
+    case tt._class:
+      return this.parseClass(true);
+
+    case tt._import:
+      return this.parseImport();
+
+    case tt._export:
+      return this.parseExport();
+
+    default:
+      var expr = this.parseExpression();
+      if (isDummy(expr)) {
+        this.next();
+        if (this.tok.type === tt.eof) return this.finishNode(node, "EmptyStatement");
+        return this.parseStatement();
+      } else if (starttype === tt.name && expr.type === "Identifier" && this.eat(tt.colon)) {
+        node.body = this.parseStatement();
+        node.label = expr;
+        return this.finishNode(node, "LabeledStatement");
+      } else {
+        node.expression = expr;
+        this.semicolon();
+        return this.finishNode(node, "ExpressionStatement");
+      }
+  }
+};
+
+lp.parseBlock = function () {
+  var node = this.startNode();
+  this.pushCx();
+  this.expect(tt.braceL);
+  var blockIndent = this.curIndent,
+      line = this.curLineStart;
+  node.body = [];
+  while (!this.closes(tt.braceR, blockIndent, line, true)) node.body.push(this.parseStatement());
+  this.popCx();
+  this.eat(tt.braceR);
+  return this.finishNode(node, "BlockStatement");
+};
+
+lp.parseFor = function (node, init) {
+  node.init = init;
+  node.test = node.update = null;
+  if (this.eat(tt.semi) && this.tok.type !== tt.semi) node.test = this.parseExpression();
+  if (this.eat(tt.semi) && this.tok.type !== tt.parenR) node.update = this.parseExpression();
+  this.popCx();
+  this.expect(tt.parenR);
+  node.body = this.parseStatement();
+  return this.finishNode(node, "ForStatement");
+};
+
+lp.parseForIn = function (node, init) {
+  var type = this.tok.type === tt._in ? "ForInStatement" : "ForOfStatement";
+  this.next();
+  node.left = init;
+  node.right = this.parseExpression();
+  this.popCx();
+  this.expect(tt.parenR);
+  node.body = this.parseStatement();
+  return this.finishNode(node, type);
+};
+
+lp.parseVar = function (noIn) {
+  var node = this.startNode();
+  node.kind = this.tok.type.keyword;
+  this.next();
+  node.declarations = [];
+  do {
+    var decl = this.startNode();
+    decl.id = this.options.ecmaVersion >= 6 ? this.toAssignable(this.parseExprAtom()) : this.parseIdent();
+    decl.init = this.eat(tt.eq) ? this.parseMaybeAssign(noIn) : null;
+    node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
+  } while (this.eat(tt.comma));
+  if (!node.declarations.length) {
+    var decl = this.startNode();
+    decl.id = this.dummyIdent();
+    node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
+  }
+  if (!noIn) this.semicolon();
+  return this.finishNode(node, "VariableDeclaration");
+};
+
+lp.parseClass = function (isStatement) {
+  var node = this.startNode();
+  this.next();
+  if (this.tok.type === tt.name) node.id = this.parseIdent();else if (isStatement) node.id = this.dummyIdent();else node.id = null;
+  node.superClass = this.eat(tt._extends) ? this.parseExpression() : null;
+  node.body = this.startNode();
+  node.body.body = [];
+  this.pushCx();
+  var indent = this.curIndent + 1,
+      line = this.curLineStart;
+  this.eat(tt.braceL);
+  if (this.curIndent + 1 < indent) {
+    indent = this.curIndent;line = this.curLineStart;
+  }
+  while (!this.closes(tt.braceR, indent, line)) {
+    if (this.semicolon()) continue;
+    var method = this.startNode(),
+        isGenerator = undefined,
+        start = undefined;
+    if (this.options.ecmaVersion >= 6) {
+      method["static"] = false;
+      isGenerator = this.eat(tt.star);
+    }
+    this.parsePropertyName(method);
+    if (isDummy(method.key)) {
+      if (isDummy(this.parseMaybeAssign())) this.next();this.eat(tt.comma);continue;
+    }
+    if (method.key.type === "Identifier" && !method.computed && method.key.name === "static" && (this.tok.type != tt.parenL && this.tok.type != tt.braceL)) {
+      method["static"] = true;
+      isGenerator = this.eat(tt.star);
+      this.parsePropertyName(method);
+    } else {
+      method["static"] = false;
+    }
+    if (this.options.ecmaVersion >= 5 && method.key.type === "Identifier" && !method.computed && (method.key.name === "get" || method.key.name === "set") && this.tok.type !== tt.parenL && this.tok.type !== tt.braceL) {
+      method.kind = method.key.name;
+      this.parsePropertyName(method);
+      method.value = this.parseMethod(false);
+    } else {
+      if (!method.computed && !method["static"] && !isGenerator && (method.key.type === "Identifier" && method.key.name === "constructor" || method.key.type === "Literal" && method.key.value === "constructor")) {
+        method.kind = "constructor";
+      } else {
+        method.kind = "method";
+      }
+      method.value = this.parseMethod(isGenerator);
+    }
+    node.body.body.push(this.finishNode(method, "MethodDefinition"));
+  }
+  this.popCx();
+  if (!this.eat(tt.braceR)) {
+    // If there is no closing brace, make the node span to the start
+    // of the next token (this is useful for Tern)
+    this.last.end = this.tok.start;
+    if (this.options.locations) this.last.loc.end = this.tok.loc.start;
+  }
+  this.semicolon();
+  this.finishNode(node.body, "ClassBody");
+  return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
+};
+
+lp.parseFunction = function (node, isStatement) {
+  this.initFunction(node);
+  if (this.options.ecmaVersion >= 6) {
+    node.generator = this.eat(tt.star);
+  }
+  if (this.tok.type === tt.name) node.id = this.parseIdent();else if (isStatement) node.id = this.dummyIdent();
+  node.params = this.parseFunctionParams();
+  node.body = this.parseBlock();
+  return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+};
+
+lp.parseExport = function () {
+  var node = this.startNode();
+  this.next();
+  if (this.eat(tt.star)) {
+    node.source = this.eatContextual("from") ? this.parseExprAtom() : null;
+    return this.finishNode(node, "ExportAllDeclaration");
+  }
+  if (this.eat(tt._default)) {
+    var expr = this.parseMaybeAssign();
+    if (expr.id) {
+      switch (expr.type) {
+        case "FunctionExpression":
+          expr.type = "FunctionDeclaration";break;
+        case "ClassExpression":
+          expr.type = "ClassDeclaration";break;
+      }
+    }
+    node.declaration = expr;
+    this.semicolon();
+    return this.finishNode(node, "ExportDefaultDeclaration");
+  }
+  if (this.tok.type.keyword) {
+    node.declaration = this.parseStatement();
+    node.specifiers = [];
+    node.source = null;
+  } else {
+    node.declaration = null;
+    node.specifiers = this.parseExportSpecifierList();
+    node.source = this.eatContextual("from") ? this.parseExprAtom() : null;
+    this.semicolon();
+  }
+  return this.finishNode(node, "ExportNamedDeclaration");
+};
+
+lp.parseImport = function () {
+  var node = this.startNode();
+  this.next();
+  if (this.tok.type === tt.string) {
+    node.specifiers = [];
+    node.source = this.parseExprAtom();
+    node.kind = "";
+  } else {
+    var elt = undefined;
+    if (this.tok.type === tt.name && this.tok.value !== "from") {
+      elt = this.startNode();
+      elt.local = this.parseIdent();
+      this.finishNode(elt, "ImportDefaultSpecifier");
+      this.eat(tt.comma);
+    }
+    node.specifiers = this.parseImportSpecifierList();
+    node.source = this.eatContextual("from") ? this.parseExprAtom() : null;
+    if (elt) node.specifiers.unshift(elt);
+  }
+  this.semicolon();
+  return this.finishNode(node, "ImportDeclaration");
+};
+
+lp.parseImportSpecifierList = function () {
+  var elts = [];
+  if (this.tok.type === tt.star) {
+    var elt = this.startNode();
+    this.next();
+    if (this.eatContextual("as")) elt.local = this.parseIdent();
+    elts.push(this.finishNode(elt, "ImportNamespaceSpecifier"));
+  } else {
+    var indent = this.curIndent,
+        line = this.curLineStart,
+        continuedLine = this.nextLineStart;
+    this.pushCx();
+    this.eat(tt.braceL);
+    if (this.curLineStart > continuedLine) continuedLine = this.curLineStart;
+    while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
+      var elt = this.startNode();
+      if (this.eat(tt.star)) {
+        if (this.eatContextual("as")) elt.local = this.parseIdent();
+        this.finishNode(elt, "ImportNamespaceSpecifier");
+      } else {
+        if (this.isContextual("from")) break;
+        elt.imported = this.parseIdent();
+        elt.local = this.eatContextual("as") ? this.parseIdent() : elt.imported;
+        this.finishNode(elt, "ImportSpecifier");
+      }
+      elts.push(elt);
+      this.eat(tt.comma);
+    }
+    this.eat(tt.braceR);
+    this.popCx();
+  }
+  return elts;
+};
+
+lp.parseExportSpecifierList = function () {
+  var elts = [];
+  var indent = this.curIndent,
+      line = this.curLineStart,
+      continuedLine = this.nextLineStart;
+  this.pushCx();
+  this.eat(tt.braceL);
+  if (this.curLineStart > continuedLine) continuedLine = this.curLineStart;
+  while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
+    if (this.isContextual("from")) break;
+    var elt = this.startNode();
+    elt.local = this.parseIdent();
+    elt.exported = this.eatContextual("as") ? this.parseIdent() : elt.local;
+    this.finishNode(elt, "ExportSpecifier");
+    elts.push(elt);
+    this.eat(tt.comma);
+  }
+  this.eat(tt.braceR);
+  this.popCx();
+  return elts;
+};
+
+},{"..":2,"./parseutil":4,"./state":5}],7:[function(require,module,exports){
+"use strict";
+
+var _ = require("..");
+
+var tt = _.tokTypes;
+var Token = _.Token;
+var isNewLine = _.isNewLine;
+var SourceLocation = _.SourceLocation;
+var getLineInfo = _.getLineInfo;
+var lineBreakG = _.lineBreakG;
+
+var LooseParser = require("./state").LooseParser;
+
+var lp = LooseParser.prototype;
+
+function isSpace(ch) {
+  return ch < 14 && ch > 8 || ch === 32 || ch === 160 || isNewLine(ch);
+}
+
+lp.next = function () {
+  this.last = this.tok;
+  if (this.ahead.length) this.tok = this.ahead.shift();else this.tok = this.readToken();
+
+  if (this.tok.start >= this.nextLineStart) {
+    while (this.tok.start >= this.nextLineStart) {
+      this.curLineStart = this.nextLineStart;
+      this.nextLineStart = this.lineEnd(this.curLineStart) + 1;
+    }
+    this.curIndent = this.indentationAfter(this.curLineStart);
+  }
+};
+
+lp.readToken = function () {
+  for (;;) {
+    try {
+      this.toks.next();
+      if (this.toks.type === tt.dot && this.input.substr(this.toks.end, 1) === "." && this.options.ecmaVersion >= 6) {
+        this.toks.end++;
+        this.toks.type = tt.ellipsis;
+      }
+      return new Token(this.toks);
+    } catch (e) {
+      if (!(e instanceof SyntaxError)) throw e;
+
+      // Try to skip some text, based on the error message, and then continue
+      var msg = e.message,
+          pos = e.raisedAt,
+          replace = true;
+      if (/unterminated/i.test(msg)) {
+        pos = this.lineEnd(e.pos + 1);
+        if (/string/.test(msg)) {
+          replace = { start: e.pos, end: pos, type: tt.string, value: this.input.slice(e.pos + 1, pos) };
+        } else if (/regular expr/i.test(msg)) {
+          var re = this.input.slice(e.pos, pos);
+          try {
+            re = new RegExp(re);
+          } catch (e) {}
+          replace = { start: e.pos, end: pos, type: tt.regexp, value: re };
+        } else if (/template/.test(msg)) {
+          replace = { start: e.pos, end: pos,
+            type: tt.template,
+            value: this.input.slice(e.pos, pos) };
+        } else {
+          replace = false;
+        }
+      } else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number/i.test(msg)) {
+        while (pos < this.input.length && !isSpace(this.input.charCodeAt(pos))) ++pos;
+      } else if (/character escape|expected hexadecimal/i.test(msg)) {
+        while (pos < this.input.length) {
+          var ch = this.input.charCodeAt(pos++);
+          if (ch === 34 || ch === 39 || isNewLine(ch)) break;
+        }
+      } else if (/unexpected character/i.test(msg)) {
+        pos++;
+        replace = false;
+      } else if (/regular expression/i.test(msg)) {
+        replace = true;
+      } else {
+        throw e;
+      }
+      this.resetTo(pos);
+      if (replace === true) replace = { start: pos, end: pos, type: tt.name, value: "✖" };
+      if (replace) {
+        if (this.options.locations) replace.loc = new SourceLocation(this.toks, getLineInfo(this.input, replace.start), getLineInfo(this.input, replace.end));
+        return replace;
+      }
+    }
+  }
+};
+
+lp.resetTo = function (pos) {
+  this.toks.pos = pos;
+  var ch = this.input.charAt(pos - 1);
+  this.toks.exprAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) || /[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(this.input.slice(pos - 10, pos));
+
+  if (this.options.locations) {
+    this.toks.curLine = 1;
+    this.toks.lineStart = lineBreakG.lastIndex = 0;
+    var match = undefined;
+    while ((match = lineBreakG.exec(this.input)) && match.index < pos) {
+      ++this.toks.curLine;
+      this.toks.lineStart = match.index + match[0].length;
+    }
+  }
+};
+
+lp.lookAhead = function (n) {
+  while (n > this.ahead.length) this.ahead.push(this.readToken());
+  return this.ahead[n - 1];
+};
+
+},{"..":2,"./state":5}]},{},[1])(1)
+});

+ 344 - 0
editor/js/libs/acorn/walk.js

@@ -0,0 +1,344 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.acorn || (g.acorn = {})).walk = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+"use strict";
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
+// AST walker module for Mozilla Parser API compatible trees
+
+// A simple walk is one where you simply specify callbacks to be
+// called on specific nodes. The last two arguments are optional. A
+// simple use would be
+//
+//     walk.simple(myTree, {
+//         Expression: function(node) { ... }
+//     });
+//
+// to do something with all expressions. All Parser API node types
+// can be used to identify node types, as well as Expression,
+// Statement, and ScopeBody, which denote categories of nodes.
+//
+// The base argument can be used to pass a custom (recursive)
+// walker, and state can be used to give this walked an initial
+// state.
+
+exports.simple = simple;
+
+// An ancestor walk builds up an array of ancestor nodes (including
+// the current node) and passes them to the callback as the state parameter.
+exports.ancestor = ancestor;
+
+// A recursive walk is one where your functions override the default
+// walkers. They can modify and replace the state parameter that's
+// threaded through the walk, and can opt how and whether to walk
+// their child nodes (by calling their third argument on these
+// nodes).
+exports.recursive = recursive;
+
+// Find a node with a given start, end, and type (all are optional,
+// null can be used as wildcard). Returns a {node, state} object, or
+// undefined when it doesn't find a matching node.
+exports.findNodeAt = findNodeAt;
+
+// Find the innermost node of a given type that contains the given
+// position. Interface similar to findNodeAt.
+exports.findNodeAround = findNodeAround;
+
+// Find the outermost matching node after a given position.
+exports.findNodeAfter = findNodeAfter;
+
+// Find the outermost matching node before a given position.
+exports.findNodeBefore = findNodeBefore;
+
+// Used to create a custom walker. Will fill in all missing node
+// type properties with the defaults.
+exports.make = make;
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+function simple(node, visitors, base, state) {
+  if (!base) base = exports.base;(function c(node, st, override) {
+    var type = override || node.type,
+        found = visitors[type];
+    base[type](node, st, c);
+    if (found) found(node, st);
+  })(node, state);
+}
+
+function ancestor(node, visitors, base, state) {
+  if (!base) base = exports.base;
+  if (!state) state = [];(function c(node, st, override) {
+    var type = override || node.type,
+        found = visitors[type];
+    if (node != st[st.length - 1]) {
+      st = st.slice();
+      st.push(node);
+    }
+    base[type](node, st, c);
+    if (found) found(node, st);
+  })(node, state);
+}
+
+function recursive(node, state, funcs, base) {
+  var visitor = funcs ? exports.make(funcs, base) : base;(function c(node, st, override) {
+    visitor[override || node.type](node, st, c);
+  })(node, state);
+}
+
+function makeTest(test) {
+  if (typeof test == "string") {
+    return function (type) {
+      return type == test;
+    };
+  } else if (!test) {
+    return function () {
+      return true;
+    };
+  } else {
+    return test;
+  }
+}
+
+var Found = function Found(node, state) {
+  _classCallCheck(this, Found);
+
+  this.node = node;this.state = state;
+};
+
+function findNodeAt(node, start, end, test, base, state) {
+  test = makeTest(test);
+  if (!base) base = exports.base;
+  try {
+    ;(function c(node, st, override) {
+      var type = override || node.type;
+      if ((start == null || node.start <= start) && (end == null || node.end >= end)) base[type](node, st, c);
+      if (test(type, node) && (start == null || node.start == start) && (end == null || node.end == end)) throw new Found(node, st);
+    })(node, state);
+  } catch (e) {
+    if (e instanceof Found) {
+      return e;
+    }throw e;
+  }
+}
+
+function findNodeAround(node, pos, test, base, state) {
+  test = makeTest(test);
+  if (!base) base = exports.base;
+  try {
+    ;(function c(node, st, override) {
+      var type = override || node.type;
+      if (node.start > pos || node.end < pos) {
+        return;
+      }base[type](node, st, c);
+      if (test(type, node)) throw new Found(node, st);
+    })(node, state);
+  } catch (e) {
+    if (e instanceof Found) {
+      return e;
+    }throw e;
+  }
+}
+
+function findNodeAfter(node, pos, test, base, state) {
+  test = makeTest(test);
+  if (!base) base = exports.base;
+  try {
+    ;(function c(node, st, override) {
+      if (node.end < pos) {
+        return;
+      }var type = override || node.type;
+      if (node.start >= pos && test(type, node)) throw new Found(node, st);
+      base[type](node, st, c);
+    })(node, state);
+  } catch (e) {
+    if (e instanceof Found) {
+      return e;
+    }throw e;
+  }
+}
+
+function findNodeBefore(node, pos, test, base, state) {
+  test = makeTest(test);
+  if (!base) base = exports.base;
+  var max = undefined;(function c(node, st, override) {
+    if (node.start > pos) {
+      return;
+    }var type = override || node.type;
+    if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node)) max = new Found(node, st);
+    base[type](node, st, c);
+  })(node, state);
+  return max;
+}
+
+function make(funcs, base) {
+  if (!base) base = exports.base;
+  var visitor = {};
+  for (var type in base) visitor[type] = base[type];
+  for (var type in funcs) visitor[type] = funcs[type];
+  return visitor;
+}
+
+function skipThrough(node, st, c) {
+  c(node, st);
+}
+function ignore(_node, _st, _c) {}
+
+// Node walkers.
+
+var base = {};
+
+exports.base = base;
+base.Program = base.BlockStatement = function (node, st, c) {
+  for (var i = 0; i < node.body.length; ++i) {
+    c(node.body[i], st, "Statement");
+  }
+};
+base.Statement = skipThrough;
+base.EmptyStatement = ignore;
+base.ExpressionStatement = base.ParenthesizedExpression = function (node, st, c) {
+  return c(node.expression, st, "Expression");
+};
+base.IfStatement = function (node, st, c) {
+  c(node.test, st, "Expression");
+  c(node.consequent, st, "Statement");
+  if (node.alternate) c(node.alternate, st, "Statement");
+};
+base.LabeledStatement = function (node, st, c) {
+  return c(node.body, st, "Statement");
+};
+base.BreakStatement = base.ContinueStatement = ignore;
+base.WithStatement = function (node, st, c) {
+  c(node.object, st, "Expression");
+  c(node.body, st, "Statement");
+};
+base.SwitchStatement = function (node, st, c) {
+  c(node.discriminant, st, "Expression");
+  for (var i = 0; i < node.cases.length; ++i) {
+    var cs = node.cases[i];
+    if (cs.test) c(cs.test, st, "Expression");
+    for (var j = 0; j < cs.consequent.length; ++j) {
+      c(cs.consequent[j], st, "Statement");
+    }
+  }
+};
+base.ReturnStatement = base.YieldExpression = function (node, st, c) {
+  if (node.argument) c(node.argument, st, "Expression");
+};
+base.ThrowStatement = base.SpreadElement = base.RestElement = function (node, st, c) {
+  return c(node.argument, st, "Expression");
+};
+base.TryStatement = function (node, st, c) {
+  c(node.block, st, "Statement");
+  if (node.handler) c(node.handler.body, st, "ScopeBody");
+  if (node.finalizer) c(node.finalizer, st, "Statement");
+};
+base.WhileStatement = base.DoWhileStatement = function (node, st, c) {
+  c(node.test, st, "Expression");
+  c(node.body, st, "Statement");
+};
+base.ForStatement = function (node, st, c) {
+  if (node.init) c(node.init, st, "ForInit");
+  if (node.test) c(node.test, st, "Expression");
+  if (node.update) c(node.update, st, "Expression");
+  c(node.body, st, "Statement");
+};
+base.ForInStatement = base.ForOfStatement = function (node, st, c) {
+  c(node.left, st, "ForInit");
+  c(node.right, st, "Expression");
+  c(node.body, st, "Statement");
+};
+base.ForInit = function (node, st, c) {
+  if (node.type == "VariableDeclaration") c(node, st);else c(node, st, "Expression");
+};
+base.DebuggerStatement = ignore;
+
+base.FunctionDeclaration = function (node, st, c) {
+  return c(node, st, "Function");
+};
+base.VariableDeclaration = function (node, st, c) {
+  for (var i = 0; i < node.declarations.length; ++i) {
+    var decl = node.declarations[i];
+    if (decl.init) c(decl.init, st, "Expression");
+  }
+};
+
+base.Function = function (node, st, c) {
+  return c(node.body, st, "ScopeBody");
+};
+base.ScopeBody = function (node, st, c) {
+  return c(node, st, "Statement");
+};
+
+base.Expression = skipThrough;
+base.ThisExpression = base.Super = base.MetaProperty = ignore;
+base.ArrayExpression = base.ArrayPattern = function (node, st, c) {
+  for (var i = 0; i < node.elements.length; ++i) {
+    var elt = node.elements[i];
+    if (elt) c(elt, st, "Expression");
+  }
+};
+base.ObjectExpression = base.ObjectPattern = function (node, st, c) {
+  for (var i = 0; i < node.properties.length; ++i) {
+    c(node.properties[i], st);
+  }
+};
+base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration;
+base.SequenceExpression = base.TemplateLiteral = function (node, st, c) {
+  for (var i = 0; i < node.expressions.length; ++i) {
+    c(node.expressions[i], st, "Expression");
+  }
+};
+base.UnaryExpression = base.UpdateExpression = function (node, st, c) {
+  c(node.argument, st, "Expression");
+};
+base.BinaryExpression = base.AssignmentExpression = base.AssignmentPattern = base.LogicalExpression = function (node, st, c) {
+  c(node.left, st, "Expression");
+  c(node.right, st, "Expression");
+};
+base.ConditionalExpression = function (node, st, c) {
+  c(node.test, st, "Expression");
+  c(node.consequent, st, "Expression");
+  c(node.alternate, st, "Expression");
+};
+base.NewExpression = base.CallExpression = function (node, st, c) {
+  c(node.callee, st, "Expression");
+  if (node.arguments) for (var i = 0; i < node.arguments.length; ++i) {
+    c(node.arguments[i], st, "Expression");
+  }
+};
+base.MemberExpression = function (node, st, c) {
+  c(node.object, st, "Expression");
+  if (node.computed) c(node.property, st, "Expression");
+};
+base.ExportDeclaration = function (node, st, c) {
+  return c(node.declaration, st);
+};
+base.ImportDeclaration = function (node, st, c) {
+  for (var i = 0; i < node.specifiers.length; i++) {
+    c(node.specifiers[i], st);
+  }
+};
+base.ImportSpecifier = base.ImportBatchSpecifier = base.Identifier = base.Literal = ignore;
+
+base.TaggedTemplateExpression = function (node, st, c) {
+  c(node.tag, st, "Expression");
+  c(node.quasi, st);
+};
+base.ClassDeclaration = base.ClassExpression = function (node, st, c) {
+  if (node.superClass) c(node.superClass, st, "Expression");
+  for (var i = 0; i < node.body.body.length; i++) {
+    c(node.body.body[i], st);
+  }
+};
+base.MethodDefinition = base.Property = function (node, st, c) {
+  if (node.computed) c(node.key, st, "Expression");
+  c(node.value, st, "Expression");
+};
+base.ComprehensionExpression = function (node, st, c) {
+  for (var i = 0; i < node.blocks.length; i++) {
+    c(node.blocks[i].right, st, "Expression");
+  }c(node.body, st, "Expression");
+};
+
+},{}]},{},[1])(1)
+});

+ 32 - 0
editor/js/libs/codemirror/addon/dialog.css

@@ -0,0 +1,32 @@
+.CodeMirror-dialog {
+  position: absolute;
+  left: 0; right: 0;
+  background: inherit;
+  z-index: 15;
+  padding: .1em .8em;
+  overflow: hidden;
+  color: inherit;
+}
+
+.CodeMirror-dialog-top {
+  border-bottom: 1px solid #eee;
+  top: 0;
+}
+
+.CodeMirror-dialog-bottom {
+  border-top: 1px solid #eee;
+  bottom: 0;
+}
+
+.CodeMirror-dialog input {
+  border: none;
+  outline: none;
+  background: transparent;
+  width: 20em;
+  color: inherit;
+  font-family: monospace;
+}
+
+.CodeMirror-dialog button {
+  font-size: 70%;
+}

+ 157 - 0
editor/js/libs/codemirror/addon/dialog.js

@@ -0,0 +1,157 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Open simple dialogs on top of an editor. Relies on dialog.css.
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  function dialogDiv(cm, template, bottom) {
+    var wrap = cm.getWrapperElement();
+    var dialog;
+    dialog = wrap.appendChild(document.createElement("div"));
+    if (bottom)
+      dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
+    else
+      dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
+
+    if (typeof template == "string") {
+      dialog.innerHTML = template;
+    } else { // Assuming it's a detached DOM element.
+      dialog.appendChild(template);
+    }
+    return dialog;
+  }
+
+  function closeNotification(cm, newVal) {
+    if (cm.state.currentNotificationClose)
+      cm.state.currentNotificationClose();
+    cm.state.currentNotificationClose = newVal;
+  }
+
+  CodeMirror.defineExtension("openDialog", function(template, callback, options) {
+    if (!options) options = {};
+
+    closeNotification(this, null);
+
+    var dialog = dialogDiv(this, template, options.bottom);
+    var closed = false, me = this;
+    function close(newVal) {
+      if (typeof newVal == 'string') {
+        inp.value = newVal;
+      } else {
+        if (closed) return;
+        closed = true;
+        dialog.parentNode.removeChild(dialog);
+        me.focus();
+
+        if (options.onClose) options.onClose(dialog);
+      }
+    }
+
+    var inp = dialog.getElementsByTagName("input")[0], button;
+    if (inp) {
+      if (options.value) {
+        inp.value = options.value;
+        if (options.selectValueOnOpen !== false) {
+          inp.select();
+        }
+      }
+
+      if (options.onInput)
+        CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
+      if (options.onKeyUp)
+        CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
+
+      CodeMirror.on(inp, "keydown", function(e) {
+        if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
+        if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
+          inp.blur();
+          CodeMirror.e_stop(e);
+          close();
+        }
+        if (e.keyCode == 13) callback(inp.value, e);
+      });
+
+      if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
+
+      inp.focus();
+    } else if (button = dialog.getElementsByTagName("button")[0]) {
+      CodeMirror.on(button, "click", function() {
+        close();
+        me.focus();
+      });
+
+      if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
+
+      button.focus();
+    }
+    return close;
+  });
+
+  CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
+    closeNotification(this, null);
+    var dialog = dialogDiv(this, template, options && options.bottom);
+    var buttons = dialog.getElementsByTagName("button");
+    var closed = false, me = this, blurring = 1;
+    function close() {
+      if (closed) return;
+      closed = true;
+      dialog.parentNode.removeChild(dialog);
+      me.focus();
+    }
+    buttons[0].focus();
+    for (var i = 0; i < buttons.length; ++i) {
+      var b = buttons[i];
+      (function(callback) {
+        CodeMirror.on(b, "click", function(e) {
+          CodeMirror.e_preventDefault(e);
+          close();
+          if (callback) callback(me);
+        });
+      })(callbacks[i]);
+      CodeMirror.on(b, "blur", function() {
+        --blurring;
+        setTimeout(function() { if (blurring <= 0) close(); }, 200);
+      });
+      CodeMirror.on(b, "focus", function() { ++blurring; });
+    }
+  });
+
+  /*
+   * openNotification
+   * Opens a notification, that can be closed with an optional timer
+   * (default 5000ms timer) and always closes on click.
+   *
+   * If a notification is opened while another is opened, it will close the
+   * currently opened one and open the new one immediately.
+   */
+  CodeMirror.defineExtension("openNotification", function(template, options) {
+    closeNotification(this, close);
+    var dialog = dialogDiv(this, template, options && options.bottom);
+    var closed = false, doneTimer;
+    var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
+
+    function close() {
+      if (closed) return;
+      closed = true;
+      clearTimeout(doneTimer);
+      dialog.parentNode.removeChild(dialog);
+    }
+
+    CodeMirror.on(dialog, 'click', function(e) {
+      CodeMirror.e_preventDefault(e);
+      close();
+    });
+
+    if (duration)
+      doneTimer = setTimeout(close, duration);
+
+    return close;
+  });
+});

+ 38 - 0
editor/js/libs/codemirror/addon/show-hint.css

@@ -0,0 +1,38 @@
+.CodeMirror-hints {
+  position: absolute;
+  z-index: 10;
+  overflow: hidden;
+  list-style: none;
+
+  margin: 0;
+  padding: 2px;
+
+  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  border-radius: 3px;
+  border: 1px solid silver;
+
+  background: white;
+  font-size: 90%;
+  font-family: monospace;
+
+  max-height: 20em;
+  overflow-y: auto;
+}
+
+.CodeMirror-hint {
+  margin: 0;
+  padding: 0 4px;
+  border-radius: 2px;
+  max-width: 19em;
+  overflow: hidden;
+  white-space: pre;
+  color: black;
+  cursor: pointer;
+}
+
+li.CodeMirror-hint-active {
+  background: #08f;
+  color: white;
+}

+ 383 - 0
editor/js/libs/codemirror/addon/show-hint.js

@@ -0,0 +1,383 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+
+  var HINT_ELEMENT_CLASS        = "CodeMirror-hint";
+  var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
+
+  // This is the old interface, kept around for now to stay
+  // backwards-compatible.
+  CodeMirror.showHint = function(cm, getHints, options) {
+    if (!getHints) return cm.showHint(options);
+    if (options && options.async) getHints.async = true;
+    var newOpts = {hint: getHints};
+    if (options) for (var prop in options) newOpts[prop] = options[prop];
+    return cm.showHint(newOpts);
+  };
+
+  CodeMirror.defineExtension("showHint", function(options) {
+    // We want a single cursor position.
+    if (this.listSelections().length > 1 || this.somethingSelected()) return;
+
+    if (this.state.completionActive) this.state.completionActive.close();
+    var completion = this.state.completionActive = new Completion(this, options);
+    if (!completion.options.hint) return;
+
+    CodeMirror.signal(this, "startCompletion", this);
+    completion.update(true);
+  });
+
+  function Completion(cm, options) {
+    this.cm = cm;
+    this.options = this.buildOptions(options);
+    this.widget = null;
+    this.debounce = 0;
+    this.tick = 0;
+    this.startPos = this.cm.getCursor();
+    this.startLen = this.cm.getLine(this.startPos.line).length;
+
+    var self = this;
+    cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
+  }
+
+  var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
+    return setTimeout(fn, 1000/60);
+  };
+  var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
+
+  Completion.prototype = {
+    close: function() {
+      if (!this.active()) return;
+      this.cm.state.completionActive = null;
+      this.tick = null;
+      this.cm.off("cursorActivity", this.activityFunc);
+
+      if (this.widget && this.data) CodeMirror.signal(this.data, "close");
+      if (this.widget) this.widget.close();
+      CodeMirror.signal(this.cm, "endCompletion", this.cm);
+    },
+
+    active: function() {
+      return this.cm.state.completionActive == this;
+    },
+
+    pick: function(data, i) {
+      var completion = data.list[i];
+      if (completion.hint) completion.hint(this.cm, data, completion);
+      else this.cm.replaceRange(getText(completion), completion.from || data.from,
+                                completion.to || data.to, "complete");
+      CodeMirror.signal(data, "pick", completion);
+      this.close();
+    },
+
+    cursorActivity: function() {
+      if (this.debounce) {
+        cancelAnimationFrame(this.debounce);
+        this.debounce = 0;
+      }
+
+      var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
+      if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
+          pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
+          (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
+        this.close();
+      } else {
+        var self = this;
+        this.debounce = requestAnimationFrame(function() {self.update();});
+        if (this.widget) this.widget.disable();
+      }
+    },
+
+    update: function(first) {
+      if (this.tick == null) return;
+      if (this.data) CodeMirror.signal(this.data, "update");
+      if (!this.options.hint.async) {
+        this.finishUpdate(this.options.hint(this.cm, this.options), first);
+      } else {
+        var myTick = ++this.tick, self = this;
+        this.options.hint(this.cm, function(data) {
+          if (self.tick == myTick) self.finishUpdate(data, first);
+        }, this.options);
+      }
+    },
+
+    finishUpdate: function(data, first) {
+      this.data = data;
+
+      var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
+      if (this.widget) this.widget.close();
+      if (data && data.list.length) {
+        if (picked && data.list.length == 1) {
+          this.pick(data, 0);
+        } else {
+          this.widget = new Widget(this, data);
+          CodeMirror.signal(data, "shown");
+        }
+      }
+    },
+
+    buildOptions: function(options) {
+      var editor = this.cm.options.hintOptions;
+      var out = {};
+      for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
+      if (editor) for (var prop in editor)
+        if (editor[prop] !== undefined) out[prop] = editor[prop];
+      if (options) for (var prop in options)
+        if (options[prop] !== undefined) out[prop] = options[prop];
+      return out;
+    }
+  };
+
+  function getText(completion) {
+    if (typeof completion == "string") return completion;
+    else return completion.text;
+  }
+
+  function buildKeyMap(completion, handle) {
+    var baseMap = {
+      Up: function() {handle.moveFocus(-1);},
+      Down: function() {handle.moveFocus(1);},
+      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
+      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
+      Home: function() {handle.setFocus(0);},
+      End: function() {handle.setFocus(handle.length - 1);},
+      Enter: handle.pick,
+      Tab: handle.pick,
+      Esc: handle.close
+    };
+    var custom = completion.options.customKeys;
+    var ourMap = custom ? {} : baseMap;
+    function addBinding(key, val) {
+      var bound;
+      if (typeof val != "string")
+        bound = function(cm) { return val(cm, handle); };
+      // This mechanism is deprecated
+      else if (baseMap.hasOwnProperty(val))
+        bound = baseMap[val];
+      else
+        bound = val;
+      ourMap[key] = bound;
+    }
+    if (custom)
+      for (var key in custom) if (custom.hasOwnProperty(key))
+        addBinding(key, custom[key]);
+    var extra = completion.options.extraKeys;
+    if (extra)
+      for (var key in extra) if (extra.hasOwnProperty(key))
+        addBinding(key, extra[key]);
+    return ourMap;
+  }
+
+  function getHintElement(hintsElement, el) {
+    while (el && el != hintsElement) {
+      if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
+      el = el.parentNode;
+    }
+  }
+
+  function Widget(completion, data) {
+    this.completion = completion;
+    this.data = data;
+    this.picked = false;
+    var widget = this, cm = completion.cm;
+
+    var hints = this.hints = document.createElement("ul");
+    hints.className = "CodeMirror-hints";
+    this.selectedHint = data.selectedHint || 0;
+
+    var completions = data.list;
+    for (var i = 0; i < completions.length; ++i) {
+      var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
+      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
+      if (cur.className != null) className = cur.className + " " + className;
+      elt.className = className;
+      if (cur.render) cur.render(elt, data, cur);
+      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+      elt.hintId = i;
+    }
+
+    var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
+    var left = pos.left, top = pos.bottom, below = true;
+    hints.style.left = left + "px";
+    hints.style.top = top + "px";
+    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
+    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
+    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+    (completion.options.container || document.body).appendChild(hints);
+    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+    if (overlapY > 0) {
+      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
+      if (curTop - height > 0) { // Fits above cursor
+        hints.style.top = (top = pos.top - height) + "px";
+        below = false;
+      } else if (height > winH) {
+        hints.style.height = (winH - 5) + "px";
+        hints.style.top = (top = pos.bottom - box.top) + "px";
+        var cursor = cm.getCursor();
+        if (data.from.ch != cursor.ch) {
+          pos = cm.cursorCoords(cursor);
+          hints.style.left = (left = pos.left) + "px";
+          box = hints.getBoundingClientRect();
+        }
+      }
+    }
+    var overlapX = box.right - winW;
+    if (overlapX > 0) {
+      if (box.right - box.left > winW) {
+        hints.style.width = (winW - 5) + "px";
+        overlapX -= (box.right - box.left) - winW;
+      }
+      hints.style.left = (left = pos.left - overlapX) + "px";
+    }
+
+    cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
+      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
+      setFocus: function(n) { widget.changeActive(n); },
+      menuSize: function() { return widget.screenAmount(); },
+      length: completions.length,
+      close: function() { completion.close(); },
+      pick: function() { widget.pick(); },
+      data: data
+    }));
+
+    if (completion.options.closeOnUnfocus) {
+      var closingOnBlur;
+      cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
+      cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
+    }
+
+    var startScroll = cm.getScrollInfo();
+    cm.on("scroll", this.onScroll = function() {
+      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
+      var newTop = top + startScroll.top - curScroll.top;
+      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+      if (!below) point += hints.offsetHeight;
+      if (point <= editor.top || point >= editor.bottom) return completion.close();
+      hints.style.top = newTop + "px";
+      hints.style.left = (left + startScroll.left - curScroll.left) + "px";
+    });
+
+    CodeMirror.on(hints, "dblclick", function(e) {
+      var t = getHintElement(hints, e.target || e.srcElement);
+      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
+    });
+
+    CodeMirror.on(hints, "click", function(e) {
+      var t = getHintElement(hints, e.target || e.srcElement);
+      if (t && t.hintId != null) {
+        widget.changeActive(t.hintId);
+        if (completion.options.completeOnSingleClick) widget.pick();
+      }
+    });
+
+    CodeMirror.on(hints, "mousedown", function() {
+      setTimeout(function(){cm.focus();}, 20);
+    });
+
+    CodeMirror.signal(data, "select", completions[0], hints.firstChild);
+    return true;
+  }
+
+  Widget.prototype = {
+    close: function() {
+      if (this.completion.widget != this) return;
+      this.completion.widget = null;
+      this.hints.parentNode.removeChild(this.hints);
+      this.completion.cm.removeKeyMap(this.keyMap);
+
+      var cm = this.completion.cm;
+      if (this.completion.options.closeOnUnfocus) {
+        cm.off("blur", this.onBlur);
+        cm.off("focus", this.onFocus);
+      }
+      cm.off("scroll", this.onScroll);
+    },
+
+    disable: function() {
+      this.completion.cm.removeKeyMap(this.keyMap);
+      var widget = this;
+      this.keyMap = {Enter: function() { widget.picked = true; }};
+      this.completion.cm.addKeyMap(this.keyMap);
+    },
+
+    pick: function() {
+      this.completion.pick(this.data, this.selectedHint);
+    },
+
+    changeActive: function(i, avoidWrap) {
+      if (i >= this.data.list.length)
+        i = avoidWrap ? this.data.list.length - 1 : 0;
+      else if (i < 0)
+        i = avoidWrap ? 0  : this.data.list.length - 1;
+      if (this.selectedHint == i) return;
+      var node = this.hints.childNodes[this.selectedHint];
+      node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+      node = this.hints.childNodes[this.selectedHint = i];
+      node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
+      if (node.offsetTop < this.hints.scrollTop)
+        this.hints.scrollTop = node.offsetTop - 3;
+      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
+        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
+      CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
+    },
+
+    screenAmount: function() {
+      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
+    }
+  };
+
+  CodeMirror.registerHelper("hint", "auto", function(cm, options) {
+    var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
+    if (helpers.length) {
+      for (var i = 0; i < helpers.length; i++) {
+        var cur = helpers[i](cm, options);
+        if (cur && cur.list.length) return cur;
+      }
+    } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
+      if (words) return CodeMirror.hint.fromList(cm, {words: words});
+    } else if (CodeMirror.hint.anyword) {
+      return CodeMirror.hint.anyword(cm, options);
+    }
+  });
+
+  CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
+    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+    var found = [];
+    for (var i = 0; i < options.words.length; i++) {
+      var word = options.words[i];
+      if (word.slice(0, token.string.length) == token.string)
+        found.push(word);
+    }
+
+    if (found.length) return {
+      list: found,
+      from: CodeMirror.Pos(cur.line, token.start),
+            to: CodeMirror.Pos(cur.line, token.end)
+    };
+  });
+
+  CodeMirror.commands.autocomplete = CodeMirror.showHint;
+
+  var defaultOptions = {
+    hint: CodeMirror.hint.auto,
+    completeSingle: true,
+    alignWithWord: true,
+    closeCharacters: /[\s()\[\]{};:>,]/,
+    closeOnUnfocus: true,
+    completeOnSingleClick: false,
+    container: null,
+    customKeys: null,
+    extraKeys: null
+  };
+
+  CodeMirror.defineOption("hintOptions", null);
+});

+ 86 - 0
editor/js/libs/codemirror/addon/tern.css

@@ -0,0 +1,86 @@
+.CodeMirror-Tern-completion {
+  padding-left: 22px;
+  position: relative;
+}
+.CodeMirror-Tern-completion:before {
+  position: absolute;
+  left: 2px;
+  bottom: 2px;
+  border-radius: 50%;
+  font-size: 12px;
+  font-weight: bold;
+  height: 15px;
+  width: 15px;
+  line-height: 16px;
+  text-align: center;
+  color: white;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+.CodeMirror-Tern-completion-unknown:before {
+  content: "?";
+  background: #4bb;
+}
+.CodeMirror-Tern-completion-object:before {
+  content: "O";
+  background: #77c;
+}
+.CodeMirror-Tern-completion-fn:before {
+  content: "F";
+  background: #7c7;
+}
+.CodeMirror-Tern-completion-array:before {
+  content: "A";
+  background: #c66;
+}
+.CodeMirror-Tern-completion-number:before {
+  content: "1";
+  background: #999;
+}
+.CodeMirror-Tern-completion-string:before {
+  content: "S";
+  background: #999;
+}
+.CodeMirror-Tern-completion-bool:before {
+  content: "B";
+  background: #999;
+}
+
+.CodeMirror-Tern-completion-guess {
+  color: #999;
+}
+
+.CodeMirror-Tern-tooltip {
+  border: 1px solid silver;
+  border-radius: 3px;
+  color: #444;
+  padding: 2px 5px;
+  font-size: 90%;
+  font-family: monospace;
+  background-color: white;
+  white-space: pre-wrap;
+
+  max-width: 40em;
+  position: absolute;
+  z-index: 10;
+  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+  box-shadow: 2px 3px 5px rgba(0,0,0,.2);
+
+  transition: opacity 1s;
+  -moz-transition: opacity 1s;
+  -webkit-transition: opacity 1s;
+  -o-transition: opacity 1s;
+  -ms-transition: opacity 1s;
+}
+
+.CodeMirror-Tern-hint-doc {
+  max-width: 25em;
+  margin-top: -3px;
+}
+
+.CodeMirror-Tern-fname { color: black; }
+.CodeMirror-Tern-farg { color: #70a; }
+.CodeMirror-Tern-farg-current { text-decoration: underline; }
+.CodeMirror-Tern-type { color: #07c; }
+.CodeMirror-Tern-fhint-guess { opacity: .7; }

+ 698 - 0
editor/js/libs/codemirror/addon/tern.js

@@ -0,0 +1,698 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Glue code between CodeMirror and Tern.
+//
+// Create a CodeMirror.TernServer to wrap an actual Tern server,
+// register open documents (CodeMirror.Doc instances) with it, and
+// call its methods to activate the assisting functions that Tern
+// provides.
+//
+// Options supported (all optional):
+// * defs: An array of JSON definition data structures.
+// * plugins: An object mapping plugin names to configuration
+//   options.
+// * getFile: A function(name, c) that can be used to access files in
+//   the project that haven't been loaded yet. Simply do c(null) to
+//   indicate that a file is not available.
+// * fileFilter: A function(value, docName, doc) that will be applied
+//   to documents before passing them on to Tern.
+// * switchToDoc: A function(name, doc) that should, when providing a
+//   multi-file view, switch the view or focus to the named file.
+// * showError: A function(editor, message) that can be used to
+//   override the way errors are displayed.
+// * completionTip: Customize the content in tooltips for completions.
+//   Is passed a single argument—the completion's data as returned by
+//   Tern—and may return a string, DOM node, or null to indicate that
+//   no tip should be shown. By default the docstring is shown.
+// * typeTip: Like completionTip, but for the tooltips shown for type
+//   queries.
+// * responseFilter: A function(doc, query, request, error, data) that
+//   will be applied to the Tern responses before treating them
+// * caseInsensitive: boolean to send case insensitive querys to tern
+//
+//
+// It is possible to run the Tern server in a web worker by specifying
+// these additional options:
+// * useWorker: Set to true to enable web worker mode. You'll probably
+//   want to feature detect the actual value you use here, for example
+//   !!window.Worker.
+// * workerScript: The main script of the worker. Point this to
+//   wherever you are hosting worker.js from this directory.
+// * workerDeps: An array of paths pointing (relative to workerScript)
+//   to the Acorn and Tern libraries and any Tern plugins you want to
+//   load. Or, if you minified those into a single script and included
+//   them in the workerScript, simply leave this undefined.
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    mod(require("../../lib/codemirror"));
+  else if (typeof define == "function" && define.amd) // AMD
+    define(["../../lib/codemirror"], mod);
+  else // Plain browser env
+    mod(CodeMirror);
+})(function(CodeMirror) {
+  "use strict";
+  // declare global: tern
+
+  CodeMirror.TernServer = function(options) {
+    var self = this;
+    this.options = options || {};
+    var plugins = this.options.plugins || (this.options.plugins = {});
+    if (!plugins.doc_comment) plugins.doc_comment = true;
+    if (this.options.useWorker) {
+      this.server = new WorkerServer(this);
+    } else {
+      this.server = new tern.Server({
+        getFile: function(name, c) { return getFile(self, name, c); },
+        async: true,
+        defs: this.options.defs || [],
+        plugins: plugins
+      });
+    }
+    this.docs = Object.create(null);
+    this.trackChange = function(doc, change) { trackChange(self, doc, change); };
+
+    this.cachedArgHints = null;
+    this.activeArgHints = null;
+    this.jumpStack = [];
+
+    this.getHint = function(cm, c) { return hint(self, cm, c); };
+    this.getHint.async = true;
+  };
+
+  CodeMirror.TernServer.prototype = {
+    addDoc: function(name, doc) {
+      var data = {doc: doc, name: name, changed: null};
+      this.server.addFile(name, docValue(this, data));
+      CodeMirror.on(doc, "change", this.trackChange);
+      return this.docs[name] = data;
+    },
+
+    delDoc: function(id) {
+      var found = resolveDoc(this, id);
+      if (!found) return;
+      CodeMirror.off(found.doc, "change", this.trackChange);
+      delete this.docs[found.name];
+      this.server.delFile(found.name);
+    },
+
+    hideDoc: function(id) {
+      closeArgHints(this);
+      var found = resolveDoc(this, id);
+      if (found && found.changed) sendDoc(this, found);
+    },
+
+    complete: function(cm) {
+      cm.showHint({hint: this.getHint});
+    },
+
+    showType: function(cm, pos, c) { showContextInfo(this, cm, pos, "type", c); },
+
+    showDocs: function(cm, pos, c) { showContextInfo(this, cm, pos, "documentation", c); },
+
+    updateArgHints: function(cm) { updateArgHints(this, cm); },
+
+    jumpToDef: function(cm) { jumpToDef(this, cm); },
+
+    jumpBack: function(cm) { jumpBack(this, cm); },
+
+    rename: function(cm) { rename(this, cm); },
+
+    selectName: function(cm) { selectName(this, cm); },
+
+    request: function (cm, query, c, pos) {
+      var self = this;
+      var doc = findDoc(this, cm.getDoc());
+      var request = buildRequest(this, doc, query, pos);
+
+      this.server.request(request, function (error, data) {
+        if (!error && self.options.responseFilter)
+          data = self.options.responseFilter(doc, query, request, error, data);
+        c(error, data);
+      });
+    },
+
+    destroy: function () {
+      if (this.worker) {
+        this.worker.terminate();
+        this.worker = null;
+      }
+    }
+  };
+
+  var Pos = CodeMirror.Pos;
+  var cls = "CodeMirror-Tern-";
+  var bigDoc = 250;
+
+  function getFile(ts, name, c) {
+    var buf = ts.docs[name];
+    if (buf)
+      c(docValue(ts, buf));
+    else if (ts.options.getFile)
+      ts.options.getFile(name, c);
+    else
+      c(null);
+  }
+
+  function findDoc(ts, doc, name) {
+    for (var n in ts.docs) {
+      var cur = ts.docs[n];
+      if (cur.doc == doc) return cur;
+    }
+    if (!name) for (var i = 0;; ++i) {
+      n = "[doc" + (i || "") + "]";
+      if (!ts.docs[n]) { name = n; break; }
+    }
+    return ts.addDoc(name, doc);
+  }
+
+  function resolveDoc(ts, id) {
+    if (typeof id == "string") return ts.docs[id];
+    if (id instanceof CodeMirror) id = id.getDoc();
+    if (id instanceof CodeMirror.Doc) return findDoc(ts, id);
+  }
+
+  function trackChange(ts, doc, change) {
+    var data = findDoc(ts, doc);
+
+    var argHints = ts.cachedArgHints;
+    if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
+      ts.cachedArgHints = null;
+
+    var changed = data.changed;
+    if (changed == null)
+      data.changed = changed = {from: change.from.line, to: change.from.line};
+    var end = change.from.line + (change.text.length - 1);
+    if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end);
+    if (end >= changed.to) changed.to = end + 1;
+    if (changed.from > change.from.line) changed.from = change.from.line;
+
+    if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() {
+      if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data);
+    }, 200);
+  }
+
+  function sendDoc(ts, doc) {
+    ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) {
+      if (error) window.console.error(error);
+      else doc.changed = null;
+    });
+  }
+
+  // Completion
+
+  function hint(ts, cm, c) {
+    ts.request(cm, {type: "completions", types: true, docs: true, urls: true, caseInsensitive: ts.options.caseInsensitive}, function(error, data) {
+      if (error) return showError(ts, cm, error);
+      var completions = [], after = "";
+      var from = data.start, to = data.end;
+      if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" &&
+          cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]")
+        after = "\"]";
+
+      for (var i = 0; i < data.completions.length; ++i) {
+        var completion = data.completions[i], className = typeToIcon(completion.type);
+        if (data.guess) className += " " + cls + "guess";
+        completions.push({text: completion.name + after,
+                          displayText: completion.name,
+                          className: className,
+                          data: completion});
+      }
+
+      var obj = {from: from, to: to, list: completions};
+      var tooltip = null;
+      CodeMirror.on(obj, "close", function() { remove(tooltip); });
+      CodeMirror.on(obj, "update", function() { remove(tooltip); });
+      CodeMirror.on(obj, "select", function(cur, node) {
+        remove(tooltip);
+        var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
+        if (content) {
+          tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
+                                node.getBoundingClientRect().top + window.pageYOffset, content);
+          tooltip.className += " " + cls + "hint-doc";
+        }
+      });
+      c(obj);
+    });
+  }
+
+  function typeToIcon(type) {
+    var suffix;
+    if (type == "?") suffix = "unknown";
+    else if (type == "number" || type == "string" || type == "bool") suffix = type;
+    else if (/^fn\(/.test(type)) suffix = "fn";
+    else if (/^\[/.test(type)) suffix = "array";
+    else suffix = "object";
+    return cls + "completion " + cls + "completion-" + suffix;
+  }
+
+  // Type queries
+
+  function showContextInfo(ts, cm, pos, queryName, c) {
+    ts.request(cm, queryName, function(error, data) {
+      if (error) return showError(ts, cm, error);
+      if (ts.options.typeTip) {
+        var tip = ts.options.typeTip(data);
+      } else {
+        var tip = elt("span", null, elt("strong", null, data.type || "not found"));
+        if (data.doc)
+          tip.appendChild(document.createTextNode(" — " + data.doc));
+        if (data.url) {
+          tip.appendChild(document.createTextNode(" "));
+          var child = tip.appendChild(elt("a", null, "[docs]"));
+          child.href = data.url;
+          child.target = "_blank";
+        }
+      }
+      tempTooltip(cm, tip);
+      if (c) c();
+    }, pos);
+  }
+
+  // Maintaining argument hints
+
+  function updateArgHints(ts, cm) {
+    closeArgHints(ts);
+
+    if (cm.somethingSelected()) return;
+    var state = cm.getTokenAt(cm.getCursor()).state;
+    var inner = CodeMirror.innerMode(cm.getMode(), state);
+    if (inner.mode.name != "javascript") return;
+    var lex = inner.state.lexical;
+    if (lex.info != "call") return;
+
+    var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize");
+    for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
+      var str = cm.getLine(line), extra = 0;
+      for (var pos = 0;;) {
+        var tab = str.indexOf("\t", pos);
+        if (tab == -1) break;
+        extra += tabSize - (tab + extra) % tabSize - 1;
+        pos = tab + 1;
+      }
+      ch = lex.column - extra;
+      if (str.charAt(ch) == "(") {found = true; break;}
+    }
+    if (!found) return;
+
+    var start = Pos(line, ch);
+    var cache = ts.cachedArgHints;
+    if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
+      return showArgHints(ts, cm, argPos);
+
+    ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
+      if (error || !data.type || !(/^fn\(/).test(data.type)) return;
+      ts.cachedArgHints = {
+        start: pos,
+        type: parseFnType(data.type),
+        name: data.exprName || data.name || "fn",
+        guess: data.guess,
+        doc: cm.getDoc()
+      };
+      showArgHints(ts, cm, argPos);
+    });
+  }
+
+  function showArgHints(ts, cm, pos) {
+    closeArgHints(ts);
+
+    var cache = ts.cachedArgHints, tp = cache.type;
+    var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
+                  elt("span", cls + "fname", cache.name), "(");
+    for (var i = 0; i < tp.args.length; ++i) {
+      if (i) tip.appendChild(document.createTextNode(", "));
+      var arg = tp.args[i];
+      tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?"));
+      if (arg.type != "?") {
+        tip.appendChild(document.createTextNode(":\u00a0"));
+        tip.appendChild(elt("span", cls + "type", arg.type));
+      }
+    }
+    tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
+    if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
+    var place = cm.cursorCoords(null, "page");
+    ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
+  }
+
+  function parseFnType(text) {
+    var args = [], pos = 3;
+
+    function skipMatching(upto) {
+      var depth = 0, start = pos;
+      for (;;) {
+        var next = text.charAt(pos);
+        if (upto.test(next) && !depth) return text.slice(start, pos);
+        if (/[{\[\(]/.test(next)) ++depth;
+        else if (/[}\]\)]/.test(next)) --depth;
+        ++pos;
+      }
+    }
+
+    // Parse arguments
+    if (text.charAt(pos) != ")") for (;;) {
+      var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
+      if (name) {
+        pos += name[0].length;
+        name = name[1];
+      }
+      args.push({name: name, type: skipMatching(/[\),]/)});
+      if (text.charAt(pos) == ")") break;
+      pos += 2;
+    }
+
+    var rettype = text.slice(pos).match(/^\) -> (.*)$/);
+
+    return {args: args, rettype: rettype && rettype[1]};
+  }
+
+  // Moving to the definition of something
+
+  function jumpToDef(ts, cm) {
+    function inner(varName) {
+      var req = {type: "definition", variable: varName || null};
+      var doc = findDoc(ts, cm.getDoc());
+      ts.server.request(buildRequest(ts, doc, req), function(error, data) {
+        if (error) return showError(ts, cm, error);
+        if (!data.file && data.url) { window.open(data.url); return; }
+
+        if (data.file) {
+          var localDoc = ts.docs[data.file], found;
+          if (localDoc && (found = findContext(localDoc.doc, data))) {
+            ts.jumpStack.push({file: doc.name,
+                               start: cm.getCursor("from"),
+                               end: cm.getCursor("to")});
+            moveTo(ts, doc, localDoc, found.start, found.end);
+            return;
+          }
+        }
+        showError(ts, cm, "Could not find a definition.");
+      });
+    }
+
+    if (!atInterestingExpression(cm))
+      dialog(cm, "Jump to variable", function(name) { if (name) inner(name); });
+    else
+      inner();
+  }
+
+  function jumpBack(ts, cm) {
+    var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file];
+    if (!doc) return;
+    moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end);
+  }
+
+  function moveTo(ts, curDoc, doc, start, end) {
+    doc.doc.setSelection(start, end);
+    if (curDoc != doc && ts.options.switchToDoc) {
+      closeArgHints(ts);
+      ts.options.switchToDoc(doc.name, doc.doc);
+    }
+  }
+
+  // The {line,ch} representation of positions makes this rather awkward.
+  function findContext(doc, data) {
+    var before = data.context.slice(0, data.contextOffset).split("\n");
+    var startLine = data.start.line - (before.length - 1);
+    var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length);
+
+    var text = doc.getLine(startLine).slice(start.ch);
+    for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur)
+      text += "\n" + doc.getLine(cur);
+    if (text.slice(0, data.context.length) == data.context) return data;
+
+    var cursor = doc.getSearchCursor(data.context, 0, false);
+    var nearest, nearestDist = Infinity;
+    while (cursor.findNext()) {
+      var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000;
+      if (!dist) dist = Math.abs(from.ch - start.ch);
+      if (dist < nearestDist) { nearest = from; nearestDist = dist; }
+    }
+    if (!nearest) return null;
+
+    if (before.length == 1)
+      nearest.ch += before[0].length;
+    else
+      nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length);
+    if (data.start.line == data.end.line)
+      var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch));
+    else
+      var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch);
+    return {start: nearest, end: end};
+  }
+
+  function atInterestingExpression(cm) {
+    var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
+    if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
+    return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
+  }
+
+  // Variable renaming
+
+  function rename(ts, cm) {
+    var token = cm.getTokenAt(cm.getCursor());
+    if (!/\w/.test(token.string)) return showError(ts, cm, "Not at a variable");
+    dialog(cm, "New name for " + token.string, function(newName) {
+      ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
+        if (error) return showError(ts, cm, error);
+        applyChanges(ts, data.changes);
+      });
+    });
+  }
+
+  function selectName(ts, cm) {
+    var name = findDoc(ts, cm.doc).name;
+    ts.request(cm, {type: "refs"}, function(error, data) {
+      if (error) return showError(ts, cm, error);
+      var ranges = [], cur = 0;
+      for (var i = 0; i < data.refs.length; i++) {
+        var ref = data.refs[i];
+        if (ref.file == name) {
+          ranges.push({anchor: ref.start, head: ref.end});
+          if (cmpPos(cur, ref.start) >= 0 && cmpPos(cur, ref.end) <= 0)
+            cur = ranges.length - 1;
+        }
+      }
+      cm.setSelections(ranges, cur);
+    });
+  }
+
+  var nextChangeOrig = 0;
+  function applyChanges(ts, changes) {
+    var perFile = Object.create(null);
+    for (var i = 0; i < changes.length; ++i) {
+      var ch = changes[i];
+      (perFile[ch.file] || (perFile[ch.file] = [])).push(ch);
+    }
+    for (var file in perFile) {
+      var known = ts.docs[file], chs = perFile[file];;
+      if (!known) continue;
+      chs.sort(function(a, b) { return cmpPos(b.start, a.start); });
+      var origin = "*rename" + (++nextChangeOrig);
+      for (var i = 0; i < chs.length; ++i) {
+        var ch = chs[i];
+        known.doc.replaceRange(ch.text, ch.start, ch.end, origin);
+      }
+    }
+  }
+
+  // Generic request-building helper
+
+  function buildRequest(ts, doc, query, pos) {
+    var files = [], offsetLines = 0, allowFragments = !query.fullDocs;
+    if (!allowFragments) delete query.fullDocs;
+    if (typeof query == "string") query = {type: query};
+    query.lineCharPositions = true;
+    if (query.end == null) {
+      query.end = pos || doc.doc.getCursor("end");
+      if (doc.doc.somethingSelected())
+        query.start = doc.doc.getCursor("start");
+    }
+    var startPos = query.start || query.end;
+
+    if (doc.changed) {
+      if (doc.doc.lineCount() > bigDoc && allowFragments !== false &&
+          doc.changed.to - doc.changed.from < 100 &&
+          doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
+        files.push(getFragmentAround(doc, startPos, query.end));
+        query.file = "#0";
+        var offsetLines = files[0].offsetLines;
+        if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
+        query.end = Pos(query.end.line - offsetLines, query.end.ch);
+      } else {
+        files.push({type: "full",
+                    name: doc.name,
+                    text: docValue(ts, doc)});
+        query.file = doc.name;
+        doc.changed = null;
+      }
+    } else {
+      query.file = doc.name;
+    }
+    for (var name in ts.docs) {
+      var cur = ts.docs[name];
+      if (cur.changed && cur != doc) {
+        files.push({type: "full", name: cur.name, text: docValue(ts, cur)});
+        cur.changed = null;
+      }
+    }
+
+    return {query: query, files: files};
+  }
+
+  function getFragmentAround(data, start, end) {
+    var doc = data.doc;
+    var minIndent = null, minLine = null, endLine, tabSize = 4;
+    for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
+      var line = doc.getLine(p), fn = line.search(/\bfunction\b/);
+      if (fn < 0) continue;
+      var indent = CodeMirror.countColumn(line, null, tabSize);
+      if (minIndent != null && minIndent <= indent) continue;
+      minIndent = indent;
+      minLine = p;
+    }
+    if (minLine == null) minLine = min;
+    var max = Math.min(doc.lastLine(), end.line + 20);
+    if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize))
+      endLine = max;
+    else for (endLine = end.line + 1; endLine < max; ++endLine) {
+      var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize);
+      if (indent <= minIndent) break;
+    }
+    var from = Pos(minLine, 0);
+
+    return {type: "part",
+            name: data.name,
+            offsetLines: from.line,
+            text: doc.getRange(from, Pos(endLine, 0))};
+  }
+
+  // Generic utilities
+
+  var cmpPos = CodeMirror.cmpPos;
+
+  function elt(tagname, cls /*, ... elts*/) {
+    var e = document.createElement(tagname);
+    if (cls) e.className = cls;
+    for (var i = 2; i < arguments.length; ++i) {
+      var elt = arguments[i];
+      if (typeof elt == "string") elt = document.createTextNode(elt);
+      e.appendChild(elt);
+    }
+    return e;
+  }
+
+  function dialog(cm, text, f) {
+    if (cm.openDialog)
+      cm.openDialog(text + ": <input type=text>", f);
+    else
+      f(prompt(text, ""));
+  }
+
+  // Tooltips
+
+  function tempTooltip(cm, content) {
+    if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
+    var where = cm.cursorCoords();
+    var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
+    function maybeClear() {
+      old = true;
+      if (!mouseOnTip) clear();
+    }
+    function clear() {
+      cm.state.ternTooltip = null;
+      if (!tip.parentNode) return;
+      cm.off("cursorActivity", clear);
+      cm.off('blur', clear);
+      cm.off('scroll', clear);
+      fadeOut(tip);
+    }
+    var mouseOnTip = false, old = false;
+    CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
+    CodeMirror.on(tip, "mouseout", function(e) {
+      if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
+        if (old) clear();
+        else mouseOnTip = false;
+      }
+    });
+    setTimeout(maybeClear, 1700);
+    cm.on("cursorActivity", clear);
+    cm.on('blur', clear);
+    cm.on('scroll', clear);
+  }
+
+  function makeTooltip(x, y, content) {
+    var node = elt("div", cls + "tooltip", content);
+    node.style.left = x + "px";
+    node.style.top = y + "px";
+    document.body.appendChild(node);
+    return node;
+  }
+
+  function remove(node) {
+    var p = node && node.parentNode;
+    if (p) p.removeChild(node);
+  }
+
+  function fadeOut(tooltip) {
+    tooltip.style.opacity = "0";
+    setTimeout(function() { remove(tooltip); }, 1100);
+  }
+
+  function showError(ts, cm, msg) {
+    if (ts.options.showError)
+      ts.options.showError(cm, msg);
+    else
+      tempTooltip(cm, String(msg));
+  }
+
+  function closeArgHints(ts) {
+    if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+  }
+
+  function docValue(ts, doc) {
+    var val = doc.doc.getValue();
+    if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
+    return val;
+  }
+
+  // Worker wrapper
+
+  function WorkerServer(ts) {
+    var worker = ts.worker = new Worker(ts.options.workerScript);
+    worker.postMessage({type: "init",
+                        defs: ts.options.defs,
+                        plugins: ts.options.plugins,
+                        scripts: ts.options.workerDeps});
+    var msgId = 0, pending = {};
+
+    function send(data, c) {
+      if (c) {
+        data.id = ++msgId;
+        pending[msgId] = c;
+      }
+      worker.postMessage(data);
+    }
+    worker.onmessage = function(e) {
+      var data = e.data;
+      if (data.type == "getFile") {
+        getFile(ts, data.name, function(err, text) {
+          send({type: "getFile", err: String(err), text: text, id: data.id});
+        });
+      } else if (data.type == "debug") {
+        window.console.log(data.message);
+      } else if (data.id && pending[data.id]) {
+        pending[data.id](data.err, data.body);
+        delete pending[data.id];
+      }
+    };
+    worker.onerror = function(e) {
+      for (var id in pending) pending[id](e);
+      pending = {};
+    };
+
+    this.addFile = function(name, text) { send({type: "add", name: name, text: text}); };
+    this.delFile = function(name) { send({type: "del", name: name}); };
+    this.request = function(body, c) { send({type: "req", body: body}, c); };
+  }
+});

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 276 - 0
editor/js/libs/tern-threejs/threejs.js


+ 87 - 0
editor/js/libs/ternjs/comment.js

@@ -0,0 +1,87 @@
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports);
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports"], mod);
+  mod(tern.comment || (tern.comment = {}));
+})(function(exports) {
+  function isSpace(ch) {
+    return (ch < 14 && ch > 8) || ch === 32 || ch === 160;
+  }
+
+  function onOwnLine(text, pos) {
+    for (; pos > 0; --pos) {
+      var ch = text.charCodeAt(pos - 1);
+      if (ch == 10) break;
+      if (!isSpace(ch)) return false;
+    }
+    return true;
+  }
+
+  // Gather comments directly before a function
+  exports.commentsBefore = function(text, pos) {
+    var found = null, emptyLines = 0, topIsLineComment;
+    out: while (pos > 0) {
+      var prev = text.charCodeAt(pos - 1);
+      if (prev == 10) {
+        for (var scan = --pos, sawNonWS = false; scan > 0; --scan) {
+          prev = text.charCodeAt(scan - 1);
+          if (prev == 47 && text.charCodeAt(scan - 2) == 47) {
+            if (!onOwnLine(text, scan - 2)) break out;
+            var content = text.slice(scan, pos);
+            if (!emptyLines && topIsLineComment) found[0] = content + "\n" + found[0];
+            else (found || (found = [])).unshift(content);
+            topIsLineComment = true;
+            emptyLines = 0;
+            pos = scan - 2;
+            break;
+          } else if (prev == 10) {
+            if (!sawNonWS && ++emptyLines > 1) break out;
+            break;
+          } else if (!sawNonWS && !isSpace(prev)) {
+            sawNonWS = true;
+          }
+        }
+      } else if (prev == 47 && text.charCodeAt(pos - 2) == 42) {
+        for (var scan = pos - 2; scan > 1; --scan) {
+          if (text.charCodeAt(scan - 1) == 42 && text.charCodeAt(scan - 2) == 47) {
+            if (!onOwnLine(text, scan - 2)) break out;
+            (found || (found = [])).unshift(text.slice(scan, pos - 2));
+            topIsLineComment = false;
+            emptyLines = 0;
+            break;
+          }
+        }
+        pos = scan - 2;
+      } else if (isSpace(prev)) {
+        --pos;
+      } else {
+        break;
+      }
+    }
+    return found;
+  };
+
+  exports.commentAfter = function(text, pos) {
+    while (pos < text.length) {
+      var next = text.charCodeAt(pos);
+      if (next == 47) {
+        var after = text.charCodeAt(pos + 1), end;
+        if (after == 47) // line comment
+          end = text.indexOf("\n", pos + 2);
+        else if (after == 42) // block comment
+          end = text.indexOf("*/", pos + 2);
+        else
+          return;
+        return text.slice(pos + 2, end < 0 ? text.length : end);
+      } else if (isSpace(next)) {
+        ++pos;
+      }
+    }
+  };
+
+  exports.ensureCommentsBefore = function(text, node) {
+    if (node.hasOwnProperty("commentsBefore")) return node.commentsBefore;
+    return node.commentsBefore = exports.commentsBefore(text, node.start);
+  };
+});

+ 589 - 0
editor/js/libs/ternjs/def.js

@@ -0,0 +1,589 @@
+// Type description parser
+//
+// Type description JSON files (such as ecma5.json and browser.json)
+// are used to
+//
+// A) describe types that come from native code
+//
+// B) to cheaply load the types for big libraries, or libraries that
+//    can't be inferred well
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return exports.init = mod;
+  if (typeof define == "function" && define.amd) // AMD
+    return define({init: mod});
+  tern.def = {init: mod};
+})(function(exports, infer) {
+  "use strict";
+
+  function hop(obj, prop) {
+    return Object.prototype.hasOwnProperty.call(obj, prop);
+  }
+
+  var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) {
+    this.pos = start || 0;
+    this.spec = spec;
+    this.base = base;
+    this.forceNew = forceNew;
+  };
+
+  function unwrapType(type, self, args) {
+    return type.call ? type(self, args) : type;
+  }
+
+  function extractProp(type, prop) {
+    if (prop == "!ret") {
+      if (type.retval) return type.retval;
+      var rv = new infer.AVal;
+      type.propagate(new infer.IsCallee(infer.ANull, [], null, rv));
+      return rv;
+    } else {
+      return type.getProp(prop);
+    }
+  }
+
+  function computedFunc(args, retType) {
+    return function(self, cArgs) {
+      var realArgs = [];
+      for (var i = 0; i < args.length; i++) realArgs.push(unwrapType(args[i], self, cArgs));
+      return new infer.Fn(name, infer.ANull, realArgs, unwrapType(retType, self, cArgs));
+    };
+  }
+  function computedUnion(types) {
+    return function(self, args) {
+      var union = new infer.AVal;
+      for (var i = 0; i < types.length; i++) unwrapType(types[i], self, args).propagate(union);
+      return union;
+    };
+  }
+  function computedArray(inner) {
+    return function(self, args) {
+      return new infer.Arr(inner(self, args));
+    };
+  }
+
+  TypeParser.prototype = {
+    eat: function(str) {
+      if (str.length == 1 ? this.spec.charAt(this.pos) == str : this.spec.indexOf(str, this.pos) == this.pos) {
+        this.pos += str.length;
+        return true;
+      }
+    },
+    word: function(re) {
+      var word = "", ch, re = re || /[\w$]/;
+      while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; }
+      return word;
+    },
+    error: function() {
+      throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")");
+    },
+    parseFnType: function(comp, name, top) {
+      var args = [], names = [], computed = false;
+      if (!this.eat(")")) for (var i = 0; ; ++i) {
+        var colon = this.spec.indexOf(": ", this.pos), argname;
+        if (colon != -1) {
+          argname = this.spec.slice(this.pos, colon);
+          if (/^[$\w?]+$/.test(argname))
+            this.pos = colon + 2;
+          else
+            argname = null;
+        }
+        names.push(argname);
+        var argType = this.parseType(comp);
+        if (argType.call) computed = true;
+        args.push(argType);
+        if (!this.eat(", ")) {
+          this.eat(")") || this.error();
+          break;
+        }
+      }
+      var retType, computeRet, computeRetStart, fn;
+      if (this.eat(" -> ")) {
+        var retStart = this.pos;
+        retType = this.parseType(true);
+        if (retType.call) {
+          if (top) {
+            computeRet = retType;
+            retType = infer.ANull;
+            computeRetStart = retStart;
+          } else {
+            computed = true;
+          }
+        }
+      } else {
+        retType = infer.ANull;
+      }
+      if (computed) return computedFunc(args, retType);
+
+      if (top && (fn = this.base))
+        infer.Fn.call(this.base, name, infer.ANull, args, names, retType);
+      else
+        fn = new infer.Fn(name, infer.ANull, args, names, retType);
+      if (computeRet) fn.computeRet = computeRet;
+      if (computeRetStart != null) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos);
+      return fn;
+    },
+    parseType: function(comp, name, top) {
+      var main = this.parseTypeMaybeProp(comp, name, top);
+      if (!this.eat("|")) return main;
+      var types = [main], computed = main.call;
+      for (;;) {
+        var next = this.parseTypeMaybeProp(comp, name, top);
+        types.push(next);
+        if (next.call) computed = true;
+        if (!this.eat("|")) break;
+      }
+      if (computed) return computedUnion(types);
+      var union = new infer.AVal;
+      for (var i = 0; i < types.length; i++) types[i].propagate(union);
+      return union;
+    },
+    parseTypeMaybeProp: function(comp, name, top) {
+      var result = this.parseTypeInner(comp, name, top);
+      while (comp && this.eat(".")) result = this.extendWithProp(result);
+      return result;
+    },
+    extendWithProp: function(base) {
+      var propName = this.word(/[\w<>$!]/) || this.error();
+      if (base.apply) return function(self, args) {
+        return extractProp(base(self, args), propName);
+      };
+      return extractProp(base, propName);
+    },
+    parseTypeInner: function(comp, name, top) {
+      if (this.eat("fn(")) {
+        return this.parseFnType(comp, name, top);
+      } else if (this.eat("[")) {
+        var inner = this.parseType(comp);
+        this.eat("]") || this.error();
+        if (inner.call) return computedArray(inner);
+        if (top && this.base) {
+          infer.Arr.call(this.base, inner);
+          return this.base;
+        }
+        return new infer.Arr(inner);
+      } else if (this.eat("+")) {
+        var path = this.word(/[\w$<>\.!]/);
+        var base = parsePath(path + ".prototype");
+        var type;
+        if (!(base instanceof infer.Obj)) base = parsePath(path);
+        if (!(base instanceof infer.Obj)) return base;
+        if (comp && this.eat("[")) return this.parsePoly(base);
+        if (top && this.forceNew) return new infer.Obj(base);
+        return infer.getInstance(base);
+      } else if (comp && this.eat("!")) {
+        var arg = this.word(/\d/);
+        if (arg) {
+          arg = Number(arg);
+          return function(_self, args) {return args[arg] || infer.ANull;};
+        } else if (this.eat("this")) {
+          return function(self) {return self;};
+        } else if (this.eat("custom:")) {
+          var fname = this.word(/[\w$]/);
+          return customFunctions[fname] || function() { return infer.ANull; };
+        } else {
+          return this.fromWord("!" + this.word(/[\w$<>\.!]/));
+        }
+      } else if (this.eat("?")) {
+        return infer.ANull;
+      } else {
+        return this.fromWord(this.word(/[\w$<>\.!`]/));
+      }
+    },
+    fromWord: function(spec) {
+      var cx = infer.cx();
+      switch (spec) {
+      case "number": return cx.num;
+      case "string": return cx.str;
+      case "bool": return cx.bool;
+      case "<top>": return cx.topScope;
+      }
+      if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec];
+      return parsePath(spec);
+    },
+    parsePoly: function(base) {
+      var propName = "<i>", match;
+      if (match = this.spec.slice(this.pos).match(/^\s*(\w+)\s*=\s*/)) {
+        propName = match[1];
+        this.pos += match[0].length;
+      }
+      var value = this.parseType(true);
+      if (!this.eat("]")) this.error();
+      if (value.call) return function(self, args) {
+        var instance = infer.getInstance(base);
+        value(self, args).propagate(instance.defProp(propName));
+        return instance;
+      };
+      var instance = infer.getInstance(base);
+      value.propagate(instance.defProp(propName));
+      return instance;
+    }
+  };
+
+  function parseType(spec, name, base, forceNew) {
+    var type = new TypeParser(spec, null, base, forceNew).parseType(false, name, true);
+    if (/^fn\(/.test(spec)) for (var i = 0; i < type.args.length; ++i) (function(i) {
+      var arg = type.args[i];
+      if (arg instanceof infer.Fn && arg.args && arg.args.length) addEffect(type, function(_self, fArgs) {
+        var fArg = fArgs[i];
+        if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null, infer.ANull));
+      });
+    })(i);
+    return type;
+  }
+
+  function addEffect(fn, handler, replaceRet) {
+    var oldCmp = fn.computeRet, rv = fn.retval;
+    fn.computeRet = function(self, args, argNodes) {
+      var handled = handler(self, args, argNodes);
+      var old = oldCmp ? oldCmp(self, args, argNodes) : rv;
+      return replaceRet ? handled : old;
+    };
+  }
+
+  var parseEffect = exports.parseEffect = function(effect, fn) {
+    var m;
+    if (effect.indexOf("propagate ") == 0) {
+      var p = new TypeParser(effect, 10);
+      var origin = p.parseType(true);
+      if (!p.eat(" ")) p.error();
+      var target = p.parseType(true);
+      addEffect(fn, function(self, args) {
+        unwrapType(origin, self, args).propagate(unwrapType(target, self, args));
+      });
+    } else if (effect.indexOf("call ") == 0) {
+      var andRet = effect.indexOf("and return ", 5) == 5;
+      var p = new TypeParser(effect, andRet ? 16 : 5);
+      var getCallee = p.parseType(true), getSelf = null, getArgs = [];
+      if (p.eat(" this=")) getSelf = p.parseType(true);
+      while (p.eat(" ")) getArgs.push(p.parseType(true));
+      addEffect(fn, function(self, args) {
+        var callee = unwrapType(getCallee, self, args);
+        var slf = getSelf ? unwrapType(getSelf, self, args) : infer.ANull, as = [];
+        for (var i = 0; i < getArgs.length; ++i) as.push(unwrapType(getArgs[i], self, args));
+        var result = andRet ? new infer.AVal : infer.ANull;
+        callee.propagate(new infer.IsCallee(slf, as, null, result));
+        return result;
+      }, andRet);
+    } else if (m = effect.match(/^custom (\S+)\s*(.*)/)) {
+      var customFunc = customFunctions[m[1]];
+      if (customFunc) addEffect(fn, m[2] ? customFunc(m[2]) : customFunc);
+    } else if (effect.indexOf("copy ") == 0) {
+      var p = new TypeParser(effect, 5);
+      var getFrom = p.parseType(true);
+      p.eat(" ");
+      var getTo = p.parseType(true);
+      addEffect(fn, function(self, args) {
+        var from = unwrapType(getFrom, self, args), to = unwrapType(getTo, self, args);
+        from.forAllProps(function(prop, val, local) {
+          if (local && prop != "<i>")
+            to.propagate(new infer.PropHasSubset(prop, val));
+        });
+      });
+    } else {
+      throw new Error("Unknown effect type: " + effect);
+    }
+  };
+
+  var currentTopScope;
+
+  var parsePath = exports.parsePath = function(path, scope) {
+    var cx = infer.cx(), cached = cx.paths[path], origPath = path;
+    if (cached != null) return cached;
+    cx.paths[path] = infer.ANull;
+
+    var base = scope || currentTopScope || cx.topScope;
+
+    if (cx.localDefs) for (var name in cx.localDefs) {
+      if (path.indexOf(name) == 0) {
+        if (path == name) return cx.paths[path] = cx.localDefs[path];
+        if (path.charAt(name.length) == ".") {
+          base = cx.localDefs[name];
+          path = path.slice(name.length + 1);
+          break;
+        }
+      }
+    }
+
+    var parts = path.split(".");
+    for (var i = 0; i < parts.length && base != infer.ANull; ++i) {
+      var prop = parts[i];
+      if (prop.charAt(0) == "!") {
+        if (prop == "!proto") {
+          base = (base instanceof infer.Obj && base.proto) || infer.ANull;
+        } else {
+          var fn = base.getFunctionType();
+          if (!fn) {
+            base = infer.ANull;
+          } else if (prop == "!ret") {
+            base = fn.retval && fn.retval.getType(false) || infer.ANull;
+          } else {
+            var arg = fn.args && fn.args[Number(prop.slice(1))];
+            base = (arg && arg.getType(false)) || infer.ANull;
+          }
+        }
+      } else if (base instanceof infer.Obj) {
+        var propVal = (prop == "prototype" && base instanceof infer.Fn) ? base.getProp(prop) : base.props[prop];
+        if (!propVal || propVal.isEmpty())
+          base = infer.ANull;
+        else
+          base = propVal.types[0];
+      }
+    }
+    // Uncomment this to get feedback on your poorly written .json files
+    // if (base == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")");
+    cx.paths[origPath] = base == infer.ANull ? null : base;
+    return base;
+  };
+
+  function emptyObj(ctor) {
+    var empty = Object.create(ctor.prototype);
+    empty.props = Object.create(null);
+    empty.isShell = true;
+    return empty;
+  }
+
+  function isSimpleAnnotation(spec) {
+    if (!spec["!type"] || /^(fn\(|\[)/.test(spec["!type"])) return false;
+    for (var prop in spec)
+      if (prop != "!type" && prop != "!doc" && prop != "!url" && prop != "!span" && prop != "!data")
+        return false;
+    return true;
+  }
+
+  function passOne(base, spec, path) {
+    if (!base) {
+      var tp = spec["!type"];
+      if (tp) {
+        if (/^fn\(/.test(tp)) base = emptyObj(infer.Fn);
+        else if (tp.charAt(0) == "[") base = emptyObj(infer.Arr);
+        else throw new Error("Invalid !type spec: " + tp);
+      } else if (spec["!stdProto"]) {
+        base = infer.cx().protos[spec["!stdProto"]];
+      } else {
+        base = emptyObj(infer.Obj);
+      }
+      base.name = path;
+    }
+
+    for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
+      var inner = spec[name];
+      if (typeof inner == "string" || isSimpleAnnotation(inner)) continue;
+      var prop = base.defProp(name);
+      passOne(prop.getObjType(), inner, path ? path + "." + name : name).propagate(prop);
+    }
+    return base;
+  }
+
+  function passTwo(base, spec, path) {
+    if (base.isShell) {
+      delete base.isShell;
+      var tp = spec["!type"];
+      if (tp) {
+        parseType(tp, path, base);
+      } else {
+        var proto = spec["!proto"] && parseType(spec["!proto"]);
+        infer.Obj.call(base, proto instanceof infer.Obj ? proto : true, path);
+      }
+    }
+
+    var effects = spec["!effects"];
+    if (effects && base instanceof infer.Fn) for (var i = 0; i < effects.length; ++i)
+      parseEffect(effects[i], base);
+    copyInfo(spec, base);
+
+    for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
+      var inner = spec[name], known = base.defProp(name), innerPath = path ? path + "." + name : name;
+      if (typeof inner == "string") {
+        if (known.isEmpty()) parseType(inner, innerPath).propagate(known);
+      } else {
+        if (!isSimpleAnnotation(inner))
+          passTwo(known.getObjType(), inner, innerPath);
+        else if (known.isEmpty())
+          parseType(inner["!type"], innerPath, null, true).propagate(known);
+        else
+          continue;
+        if (inner["!doc"]) known.doc = inner["!doc"];
+        if (inner["!url"]) known.url = inner["!url"];
+        if (inner["!span"]) known.span = inner["!span"];
+      }
+    }
+    return base;
+  }
+
+  function copyInfo(spec, type) {
+    if (spec["!doc"]) type.doc = spec["!doc"];
+    if (spec["!url"]) type.url = spec["!url"];
+    if (spec["!span"]) type.span = spec["!span"];
+    if (spec["!data"]) type.metaData = spec["!data"];
+  }
+
+  function runPasses(type, arg) {
+    var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type];
+    if (pass) for (var i = 0; i < pass.length; i++) pass[i](arg);
+  }
+
+  function doLoadEnvironment(data, scope) {
+    var cx = infer.cx();
+
+    infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length);
+    cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null);
+
+    runPasses("preLoadDef", data);
+
+    passOne(scope, data);
+
+    var def = data["!define"];
+    if (def) {
+      for (var name in def) {
+        var spec = def[name];
+        cx.localDefs[name] = typeof spec == "string" ? parsePath(spec) : passOne(null, spec, name);
+      }
+      for (var name in def) {
+        var spec = def[name];
+        if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name);
+      }
+    }
+
+    passTwo(scope, data);
+
+    runPasses("postLoadDef", data);
+
+    cx.curOrigin = cx.localDefs = null;
+  }
+
+  exports.load = function(data, scope) {
+    if (!scope) scope = infer.cx().topScope;
+    var oldScope = currentTopScope;
+    currentTopScope = scope;
+    try {
+      doLoadEnvironment(data, scope);
+    } finally {
+      currentTopScope = oldScope;
+    }
+  };
+
+  exports.parse = function(data, origin, path) {
+    var cx = infer.cx();
+    if (origin) {
+      cx.origin = origin;
+      cx.localDefs = cx.definitions[origin];
+    }
+
+    try {
+      if (typeof data == "string")
+        return parseType(data, path);
+      else
+        return passTwo(passOne(null, data, path), data, path);
+    } finally {
+      if (origin) cx.origin = cx.localDefs = null;
+    }
+  };
+
+  // Used to register custom logic for more involved effect or type
+  // computation.
+  var customFunctions = Object.create(null);
+  infer.registerFunction = function(name, f) { customFunctions[name] = f; };
+
+  var IsCreated = infer.constraint("created, target, spec", {
+    addType: function(tp) {
+      if (tp instanceof infer.Obj && this.created++ < 5) {
+        var derived = new infer.Obj(tp), spec = this.spec;
+        if (spec instanceof infer.AVal) spec = spec.getObjType(false);
+        if (spec instanceof infer.Obj) for (var prop in spec.props) {
+          var cur = spec.props[prop].types[0];
+          var p = derived.defProp(prop);
+          if (cur && cur instanceof infer.Obj && cur.props.value) {
+            var vtp = cur.props.value.getType(false);
+            if (vtp) p.addType(vtp);
+          }
+        }
+        this.target.addType(derived);
+      }
+    }
+  });
+
+  infer.registerFunction("Object_create", function(_self, args, argNodes) {
+    if (argNodes && argNodes.length && argNodes[0].type == "Literal" && argNodes[0].value == null)
+      return new infer.Obj();
+
+    var result = new infer.AVal;
+    if (args[0]) args[0].propagate(new IsCreated(0, result, args[1]));
+    return result;
+  });
+
+  var PropSpec = infer.constraint("target", {
+    addType: function(tp) {
+      if (!(tp instanceof infer.Obj)) return;
+      if (tp.hasProp("value"))
+        tp.getProp("value").propagate(this.target);
+      else if (tp.hasProp("get"))
+        tp.getProp("get").propagate(new infer.IsCallee(infer.ANull, [], null, this.target));
+    }
+  });
+
+  infer.registerFunction("Object_defineProperty", function(_self, args, argNodes) {
+    if (argNodes && argNodes.length >= 3 && argNodes[1].type == "Literal" &&
+        typeof argNodes[1].value == "string") {
+      var obj = args[0], connect = new infer.AVal;
+      obj.propagate(new infer.PropHasSubset(argNodes[1].value, connect, argNodes[1]));
+      args[2].propagate(new PropSpec(connect));
+    }
+    return infer.ANull;
+  });
+
+  infer.registerFunction("Object_defineProperties", function(_self, args, argNodes) {
+    if (args.length >= 2) {
+      var obj = args[0];
+      args[1].forAllProps(function(prop, val, local) {
+        if (!local) return;
+        var connect = new infer.AVal;
+        obj.propagate(new infer.PropHasSubset(prop, connect, argNodes && argNodes[1]));
+        val.propagate(new PropSpec(connect));
+      });
+    }
+    return infer.ANull;
+  });
+
+  var IsBound = infer.constraint("self, args, target", {
+    addType: function(tp) {
+      if (!(tp instanceof infer.Fn)) return;
+      this.target.addType(new infer.Fn(tp.name, infer.ANull, tp.args.slice(this.args.length),
+                                       tp.argNames.slice(this.args.length), tp.retval));
+      this.self.propagate(tp.self);
+      for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i)
+        this.args[i].propagate(tp.args[i]);
+    }
+  });
+
+  infer.registerFunction("Function_bind", function(self, args) {
+    if (!args.length) return infer.ANull;
+    var result = new infer.AVal;
+    self.propagate(new IsBound(args[0], args.slice(1), result));
+    return result;
+  });
+
+  infer.registerFunction("Array_ctor", function(_self, args) {
+    var arr = new infer.Arr;
+    if (args.length != 1 || !args[0].hasType(infer.cx().num)) {
+      var content = arr.getProp("<i>");
+      for (var i = 0; i < args.length; ++i) args[i].propagate(content);
+    }
+    return arr;
+  });
+
+  infer.registerFunction("Promise_ctor", function(_self, args, argNodes) {
+    if (args.length < 1) return infer.ANull;
+    var self = new infer.Obj(infer.cx().definitions.ecma6["Promise.prototype"]);
+    var valProp = self.defProp("value", argNodes && argNodes[0]);
+    var valArg = new infer.AVal;
+    valArg.propagate(valProp);
+    var exec = new infer.Fn("execute", infer.ANull, [valArg], ["value"], infer.ANull);
+    var reject = infer.cx().definitions.ecma6.promiseReject;
+    args[0].propagate(new infer.IsCallee(infer.ANull, [exec, reject], null, infer.ANull));
+    return self;
+  });
+
+  return exports;
+});

+ 402 - 0
editor/js/libs/ternjs/doc_comment.js

@@ -0,0 +1,402 @@
+// Parses comments above variable declarations, function declarations,
+// and object properties as docstrings and JSDoc-style type
+// annotations.
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(require("../lib/infer"), require("../lib/tern"), require("../lib/comment"),
+               require("acorn"), require("acorn/dist/walk"));
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["../lib/infer", "../lib/tern", "../lib/comment", "acorn/dist/acorn", "acorn/dist/walk"], mod);
+  mod(tern, tern, tern.comment, acorn, acorn.walk);
+})(function(infer, tern, comment, acorn, walk) {
+  "use strict";
+
+  var WG_MADEUP = 1, WG_STRONG = 101;
+
+  tern.registerPlugin("doc_comment", function(server, options) {
+    server.jsdocTypedefs = Object.create(null);
+    server.on("reset", function() {
+      server.jsdocTypedefs = Object.create(null);
+    });
+    server._docComment = {
+      weight: options && options.strong ? WG_STRONG : undefined,
+      fullDocs: options && options.fullDocs
+    };
+
+    return {
+      passes: {
+        postParse: postParse,
+        postInfer: postInfer,
+        postLoadDef: postLoadDef
+      }
+    };
+  });
+
+  function postParse(ast, text) {
+    function attachComments(node) { comment.ensureCommentsBefore(text, node); }
+
+    walk.simple(ast, {
+      VariableDeclaration: attachComments,
+      FunctionDeclaration: attachComments,
+      AssignmentExpression: function(node) {
+        if (node.operator == "=") attachComments(node);
+      },
+      ObjectExpression: function(node) {
+        for (var i = 0; i < node.properties.length; ++i)
+          attachComments(node.properties[i]);
+      },
+      CallExpression: function(node) {
+        if (isDefinePropertyCall(node)) attachComments(node);
+      }
+    });
+  }
+
+  function isDefinePropertyCall(node) {
+    return node.callee.type == "MemberExpression" &&
+      node.callee.object.name == "Object" &&
+      node.callee.property.name == "defineProperty" &&
+      node.arguments.length >= 3 &&
+      typeof node.arguments[1].value == "string";
+  }
+
+  function postInfer(ast, scope) {
+    jsdocParseTypedefs(ast.sourceFile.text, scope);
+
+    walk.simple(ast, {
+      VariableDeclaration: function(node, scope) {
+        if (node.commentsBefore)
+          interpretComments(node, node.commentsBefore, scope,
+                            scope.getProp(node.declarations[0].id.name));
+      },
+      FunctionDeclaration: function(node, scope) {
+        if (node.commentsBefore)
+          interpretComments(node, node.commentsBefore, scope,
+                            scope.getProp(node.id.name),
+                            node.body.scope.fnType);
+      },
+      AssignmentExpression: function(node, scope) {
+        if (node.commentsBefore)
+          interpretComments(node, node.commentsBefore, scope,
+                            infer.expressionType({node: node.left, state: scope}));
+      },
+      ObjectExpression: function(node, scope) {
+        for (var i = 0; i < node.properties.length; ++i) {
+          var prop = node.properties[i];
+          if (prop.commentsBefore)
+            interpretComments(prop, prop.commentsBefore, scope,
+                              node.objType.getProp(prop.key.name));
+        }
+      },
+      CallExpression: function(node, scope) {
+        if (node.commentsBefore && isDefinePropertyCall(node)) {
+          var type = infer.expressionType({node: node.arguments[0], state: scope}).getObjType();
+          if (type && type instanceof infer.Obj) {
+            var prop = type.props[node.arguments[1].value];
+            if (prop) interpretComments(node, node.commentsBefore, scope, prop);
+          }
+        }
+      }
+    }, infer.searchVisitor, scope);
+  }
+
+  function postLoadDef(data) {
+    var defs = data["!typedef"];
+    var cx = infer.cx(), orig = data["!name"];
+    if (defs) for (var name in defs)
+      cx.parent.jsdocTypedefs[name] =
+        maybeInstance(infer.def.parse(defs[name], orig, name), name);
+  }
+
+  // COMMENT INTERPRETATION
+
+  function interpretComments(node, comments, scope, aval, type) {
+    jsdocInterpretComments(node, scope, aval, comments);
+    var cx = infer.cx();
+
+    if (!type && aval instanceof infer.AVal && aval.types.length) {
+      type = aval.types[aval.types.length - 1];
+      if (!(type instanceof infer.Obj) || type.origin != cx.curOrigin || type.doc)
+        type = null;
+    }
+
+    var result = comments[comments.length - 1];
+    if (cx.parent._docComment.fullDocs) {
+      result = result.trim().replace(/\n[ \t]*\* ?/g, "\n");
+    } else {
+      var dot = result.search(/\.\s/);
+      if (dot > 5) result = result.slice(0, dot + 1);
+      result = result.trim().replace(/\s*\n\s*\*\s*|\s{1,}/g, " ");
+    }
+    result = result.replace(/^\s*\*+\s*/, "");
+
+    if (aval instanceof infer.AVal) aval.doc = result;
+    if (type) type.doc = result;
+  }
+
+  // Parses a subset of JSDoc-style comments in order to include the
+  // explicitly defined types in the analysis.
+
+  function skipSpace(str, pos) {
+    while (/\s/.test(str.charAt(pos))) ++pos;
+    return pos;
+  }
+
+  function isIdentifier(string) {
+    if (!acorn.isIdentifierStart(string.charCodeAt(0))) return false;
+    for (var i = 1; i < string.length; i++)
+      if (!acorn.isIdentifierChar(string.charCodeAt(i))) return false;
+    return true;
+  }
+
+  function parseLabelList(scope, str, pos, close) {
+    var labels = [], types = [], madeUp = false;
+    for (var first = true; ; first = false) {
+      pos = skipSpace(str, pos);
+      if (first && str.charAt(pos) == close) break;
+      var colon = str.indexOf(":", pos);
+      if (colon < 0) return null;
+      var label = str.slice(pos, colon);
+      if (!isIdentifier(label)) return null;
+      labels.push(label);
+      pos = colon + 1;
+      var type = parseType(scope, str, pos);
+      if (!type) return null;
+      pos = type.end;
+      madeUp = madeUp || type.madeUp;
+      types.push(type.type);
+      pos = skipSpace(str, pos);
+      var next = str.charAt(pos);
+      ++pos;
+      if (next == close) break;
+      if (next != ",") return null;
+    }
+    return {labels: labels, types: types, end: pos, madeUp: madeUp};
+  }
+
+  function parseType(scope, str, pos) {
+    var type, union = false, madeUp = false;
+    for (;;) {
+      var inner = parseTypeInner(scope, str, pos);
+      if (!inner) return null;
+      madeUp = madeUp || inner.madeUp;
+      if (union) inner.type.propagate(union);
+      else type = inner.type;
+      pos = skipSpace(str, inner.end);
+      if (str.charAt(pos) != "|") break;
+      pos++;
+      if (!union) {
+        union = new infer.AVal;
+        type.propagate(union);
+        type = union;
+      }
+    }
+    var isOptional = false;
+    if (str.charAt(pos) == "=") {
+      ++pos;
+      isOptional = true;
+    }
+    return {type: type, end: pos, isOptional: isOptional, madeUp: madeUp};
+  }
+
+  function parseTypeInner(scope, str, pos) {
+    pos = skipSpace(str, pos);
+    var type, madeUp = false;
+
+    if (str.indexOf("function(", pos) == pos) {
+      var args = parseLabelList(scope, str, pos + 9, ")"), ret = infer.ANull;
+      if (!args) return null;
+      pos = skipSpace(str, args.end);
+      if (str.charAt(pos) == ":") {
+        ++pos;
+        var retType = parseType(scope, str, pos + 1);
+        if (!retType) return null;
+        pos = retType.end;
+        ret = retType.type;
+        madeUp = retType.madeUp;
+      }
+      type = new infer.Fn(null, infer.ANull, args.types, args.labels, ret);
+    } else if (str.charAt(pos) == "[") {
+      var inner = parseType(scope, str, pos + 1);
+      if (!inner) return null;
+      pos = skipSpace(str, inner.end);
+      madeUp = inner.madeUp;
+      if (str.charAt(pos) != "]") return null;
+      ++pos;
+      type = new infer.Arr(inner.type);
+    } else if (str.charAt(pos) == "{") {
+      var fields = parseLabelList(scope, str, pos + 1, "}");
+      if (!fields) return null;
+      type = new infer.Obj(true);
+      for (var i = 0; i < fields.types.length; ++i) {
+        var field = type.defProp(fields.labels[i]);
+        field.initializer = true;
+        fields.types[i].propagate(field);
+      }
+      pos = fields.end;
+      madeUp = fields.madeUp;
+    } else if (str.charAt(pos) == "(") {
+      var inner = parseType(scope, str, pos + 1);
+      if (!inner) return null;
+      pos = skipSpace(str, inner.end);
+      if (str.charAt(pos) != ")") return null;
+      ++pos;
+      type = inner.type;
+    } else {
+      var start = pos;
+      if (!acorn.isIdentifierStart(str.charCodeAt(pos))) return null;
+      while (acorn.isIdentifierChar(str.charCodeAt(pos))) ++pos;
+      if (start == pos) return null;
+      var word = str.slice(start, pos);
+      if (/^(number|integer)$/i.test(word)) type = infer.cx().num;
+      else if (/^bool(ean)?$/i.test(word)) type = infer.cx().bool;
+      else if (/^string$/i.test(word)) type = infer.cx().str;
+      else if (/^(null|undefined)$/i.test(word)) type = infer.ANull;
+      else if (/^array$/i.test(word)) {
+        var inner = null;
+        if (str.charAt(pos) == "." && str.charAt(pos + 1) == "<") {
+          var inAngles = parseType(scope, str, pos + 2);
+          if (!inAngles) return null;
+          pos = skipSpace(str, inAngles.end);
+          madeUp = inAngles.madeUp;
+          if (str.charAt(pos++) != ">") return null;
+          inner = inAngles.type;
+        }
+        type = new infer.Arr(inner);
+      } else if (/^object$/i.test(word)) {
+        type = new infer.Obj(true);
+        if (str.charAt(pos) == "." && str.charAt(pos + 1) == "<") {
+          var key = parseType(scope, str, pos + 2);
+          if (!key) return null;
+          pos = skipSpace(str, key.end);
+          madeUp = madeUp || key.madeUp;
+          if (str.charAt(pos++) != ",") return null;
+          var val = parseType(scope, str, pos);
+          if (!val) return null;
+          pos = skipSpace(str, val.end);
+          madeUp = key.madeUp || val.madeUp;
+          if (str.charAt(pos++) != ">") return null;
+          val.type.propagate(type.defProp("<i>"));
+        }
+      } else {
+        while (str.charCodeAt(pos) == 46 ||
+               acorn.isIdentifierChar(str.charCodeAt(pos))) ++pos;
+        var path = str.slice(start, pos);
+        var cx = infer.cx(), defs = cx.parent && cx.parent.jsdocTypedefs, found;
+        if (defs && (path in defs)) {
+          type = defs[path];
+        } else if (found = infer.def.parsePath(path, scope).getObjType()) {
+          type = maybeInstance(found, path);
+        } else {
+          if (!cx.jsdocPlaceholders) cx.jsdocPlaceholders = Object.create(null);
+          if (!(path in cx.jsdocPlaceholders))
+            type = cx.jsdocPlaceholders[path] = new infer.Obj(null, path);
+          else
+            type = cx.jsdocPlaceholders[path];
+          madeUp = true;
+        }
+      }
+    }
+
+    return {type: type, end: pos, madeUp: madeUp};
+  }
+
+  function maybeInstance(type, path) {
+    if (type instanceof infer.Fn && /^[A-Z]/.test(path)) {
+      var proto = type.getProp("prototype").getObjType();
+      if (proto instanceof infer.Obj) return infer.getInstance(proto);
+    }
+    return type;
+  }
+
+  function parseTypeOuter(scope, str, pos) {
+    pos = skipSpace(str, pos || 0);
+    if (str.charAt(pos) != "{") return null;
+    var result = parseType(scope, str, pos + 1);
+    if (!result) return null;
+    var end = skipSpace(str, result.end);
+    if (str.charAt(end) != "}") return null;
+    result.end = end + 1;
+    return result;
+  }
+
+  function jsdocInterpretComments(node, scope, aval, comments) {
+    var type, args, ret, foundOne, self, parsed;
+
+    for (var i = 0; i < comments.length; ++i) {
+      var comment = comments[i];
+      var decl = /(?:\n|$|\*)\s*@(type|param|arg(?:ument)?|returns?|this)\s+(.*)/g, m;
+      while (m = decl.exec(comment)) {
+        if (m[1] == "this" && (parsed = parseType(scope, m[2], 0))) {
+          self = parsed;
+          foundOne = true;
+          continue;
+        }
+
+        if (!(parsed = parseTypeOuter(scope, m[2]))) continue;
+        foundOne = true;
+
+        switch(m[1]) {
+        case "returns": case "return":
+          ret = parsed; break;
+        case "type":
+          type = parsed; break;
+        case "param": case "arg": case "argument":
+            var name = m[2].slice(parsed.end).match(/^\s*(\[?)\s*([^\]\s=]+)\s*(?:=[^\]]+\s*)?(\]?).*/);
+            if (!name) continue;
+            var argname = name[2] + (parsed.isOptional || (name[1] === '[' && name[3] === ']') ? "?" : "");
+          (args || (args = Object.create(null)))[argname] = parsed;
+          break;
+        }
+      }
+    }
+
+    if (foundOne) applyType(type, self, args, ret, node, aval);
+  };
+
+  function jsdocParseTypedefs(text, scope) {
+    var cx = infer.cx();
+
+    var re = /\s@typedef\s+(.*)/g, m;
+    while (m = re.exec(text)) {
+      var parsed = parseTypeOuter(scope, m[1]);
+      var name = parsed && m[1].slice(parsed.end).match(/^\s*(\S+)/);
+      if (name)
+        cx.parent.jsdocTypedefs[name[1]] = parsed.type;
+    }
+  }
+
+  function propagateWithWeight(type, target) {
+    var weight = infer.cx().parent._docComment.weight;
+    type.type.propagate(target, weight || (type.madeUp ? WG_MADEUP : undefined));
+  }
+
+  function applyType(type, self, args, ret, node, aval) {
+    var fn;
+    if (node.type == "VariableDeclaration") {
+      var decl = node.declarations[0];
+      if (decl.init && decl.init.type == "FunctionExpression") fn = decl.init.body.scope.fnType;
+    } else if (node.type == "FunctionDeclaration") {
+      fn = node.body.scope.fnType;
+    } else if (node.type == "AssignmentExpression") {
+      if (node.right.type == "FunctionExpression")
+        fn = node.right.body.scope.fnType;
+    } else if (node.type == "CallExpression") {
+    } else { // An object property
+      if (node.value.type == "FunctionExpression") fn = node.value.body.scope.fnType;
+    }
+
+    if (fn && (args || ret || self)) {
+      if (args) for (var i = 0; i < fn.argNames.length; ++i) {
+        var name = fn.argNames[i], known = args[name];
+        if (!known && (known = args[name + "?"]))
+          fn.argNames[i] += "?";
+        if (known) propagateWithWeight(known, fn.args[i]);
+      }
+      if (ret) propagateWithWeight(ret, fn.retval);
+      if (self) propagateWithWeight(self, fn.self);
+    } else if (type) {
+      propagateWithWeight(type, aval);
+    }
+  };
+});

+ 1635 - 0
editor/js/libs/ternjs/infer.js

@@ -0,0 +1,1635 @@
+// Main type inference engine
+
+// Walks an AST, building up a graph of abstract values and constraints
+// that cause types to flow from one node to another. Also defines a
+// number of utilities for accessing ASTs and scopes.
+
+// Analysis is done in a context, which is tracked by the dynamically
+// bound cx variable. Use withContext to set the current context.
+
+// For memory-saving reasons, individual types export an interface
+// similar to abstract values (which can hold multiple types), and can
+// thus be used in place abstract values that only ever contain a
+// single type.
+
+(function(root, mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports, require("acorn"), require("acorn/dist/acorn_loose"), require("acorn/dist/walk"),
+               require("./def"), require("./signal"));
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports", "acorn/dist/acorn", "acorn/dist/acorn_loose", "acorn/dist/walk", "./def", "./signal"], mod);
+  mod(root.tern || (root.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env
+})(this, function(exports, acorn, acorn_loose, walk, def, signal) {
+  "use strict";
+
+  var toString = exports.toString = function(type, maxDepth, parent) {
+    if (!type || type == parent || maxDepth && maxDepth < -3) return "?";
+    return type.toString(maxDepth, parent);
+  };
+
+  // A variant of AVal used for unknown, dead-end values. Also serves
+  // as prototype for AVals, Types, and Constraints because it
+  // implements 'empty' versions of all the methods that the code
+  // expects.
+  var ANull = exports.ANull = signal.mixin({
+    addType: function() {},
+    propagate: function() {},
+    getProp: function() { return ANull; },
+    forAllProps: function() {},
+    hasType: function() { return false; },
+    isEmpty: function() { return true; },
+    getFunctionType: function() {},
+    getObjType: function() {},
+    getType: function() {},
+    gatherProperties: function() {},
+    propagatesTo: function() {},
+    typeHint: function() {},
+    propHint: function() {},
+    toString: function() { return "?"; }
+  });
+
+  function extend(proto, props) {
+    var obj = Object.create(proto);
+    if (props) for (var prop in props) obj[prop] = props[prop];
+    return obj;
+  }
+
+  // ABSTRACT VALUES
+
+  var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10, WG_MULTI_MEMBER = 5,
+      WG_CATCH_ERROR = 5, WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2;
+
+  var AVal = exports.AVal = function() {
+    this.types = [];
+    this.forward = null;
+    this.maxWeight = 0;
+  };
+  AVal.prototype = extend(ANull, {
+    addType: function(type, weight) {
+      weight = weight || WG_DEFAULT;
+      if (this.maxWeight < weight) {
+        this.maxWeight = weight;
+        if (this.types.length == 1 && this.types[0] == type) return;
+        this.types.length = 0;
+      } else if (this.maxWeight > weight || this.types.indexOf(type) > -1) {
+        return;
+      }
+
+      this.signal("addType", type);
+      this.types.push(type);
+      var forward = this.forward;
+      if (forward) withWorklist(function(add) {
+        for (var i = 0; i < forward.length; ++i) add(type, forward[i], weight);
+      });
+    },
+
+    propagate: function(target, weight) {
+      if (target == ANull || (target instanceof Type && this.forward && this.forward.length > 2)) return;
+      if (weight && weight != WG_DEFAULT) target = new Muffle(target, weight);
+      (this.forward || (this.forward = [])).push(target);
+      var types = this.types;
+      if (types.length) withWorklist(function(add) {
+        for (var i = 0; i < types.length; ++i) add(types[i], target, weight);
+      });
+    },
+
+    getProp: function(prop) {
+      if (prop == "__proto__" || prop == "✖") return ANull;
+      var found = (this.props || (this.props = Object.create(null)))[prop];
+      if (!found) {
+        found = this.props[prop] = new AVal;
+        this.propagate(new PropIsSubset(prop, found));
+      }
+      return found;
+    },
+
+    forAllProps: function(c) {
+      this.propagate(new ForAllProps(c));
+    },
+
+    hasType: function(type) {
+      return this.types.indexOf(type) > -1;
+    },
+    isEmpty: function() { return this.types.length === 0; },
+    getFunctionType: function() {
+      for (var i = this.types.length - 1; i >= 0; --i)
+        if (this.types[i] instanceof Fn) return this.types[i];
+    },
+    getObjType: function() {
+      var seen = null;
+      for (var i = this.types.length - 1; i >= 0; --i) {
+        var type = this.types[i];
+        if (!(type instanceof Obj)) continue;
+        if (type.name) return type;
+        if (!seen) seen = type;
+      }
+      return seen;
+    },
+
+    getType: function(guess) {
+      if (this.types.length === 0 && guess !== false) return this.makeupType();
+      if (this.types.length === 1) return this.types[0];
+      return canonicalType(this.types);
+    },
+
+    toString: function(maxDepth, parent) {
+      if (this.types.length == 0) return toString(this.makeupType(), maxDepth, parent);
+      if (this.types.length == 1) return toString(this.types[0], maxDepth, parent);
+      var simplified = simplifyTypes(this.types);
+      if (simplified.length > 2) return "?";
+      return simplified.map(function(tp) { return toString(tp, maxDepth, parent); }).join("|");
+    },
+
+    computedPropType: function() {
+      if (!this.propertyOf) return null;
+      if (this.propertyOf.hasProp("<i>")) {
+        var computedProp = this.propertyOf.getProp("<i>");
+        if (computedProp == this) return null;
+        return computedProp.getType();
+      } else if (this.propertyOf.maybeProps && this.propertyOf.maybeProps["<i>"] == this) {
+        for (var prop in this.propertyOf.props) {
+          var val = this.propertyOf.props[prop];
+          if (!val.isEmpty()) return val;
+        }
+        return null;
+      }
+    },
+
+    makeupType: function() {
+      var computed = this.computedPropType();
+      if (computed) return computed;
+
+      if (!this.forward) return null;
+      for (var i = this.forward.length - 1; i >= 0; --i) {
+        var hint = this.forward[i].typeHint();
+        if (hint && !hint.isEmpty()) {guessing = true; return hint;}
+      }
+
+      var props = Object.create(null), foundProp = null;
+      for (var i = 0; i < this.forward.length; ++i) {
+        var prop = this.forward[i].propHint();
+        if (prop && prop != "length" && prop != "<i>" && prop != "✖" && prop != cx.completingProperty) {
+          props[prop] = true;
+          foundProp = prop;
+        }
+      }
+      if (!foundProp) return null;
+
+      var objs = objsWithProp(foundProp);
+      if (objs) {
+        var matches = [];
+        search: for (var i = 0; i < objs.length; ++i) {
+          var obj = objs[i];
+          for (var prop in props) if (!obj.hasProp(prop)) continue search;
+          if (obj.hasCtor) obj = getInstance(obj);
+          matches.push(obj);
+        }
+        var canon = canonicalType(matches);
+        if (canon) {guessing = true; return canon;}
+      }
+    },
+
+    typeHint: function() { return this.types.length ? this.getType() : null; },
+    propagatesTo: function() { return this; },
+
+    gatherProperties: function(f, depth) {
+      for (var i = 0; i < this.types.length; ++i)
+        this.types[i].gatherProperties(f, depth);
+    },
+
+    guessProperties: function(f) {
+      if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
+        var prop = this.forward[i].propHint();
+        if (prop) f(prop, null, 0);
+      }
+      var guessed = this.makeupType();
+      if (guessed) guessed.gatherProperties(f);
+    }
+  });
+
+  function similarAVal(a, b, depth) {
+    var typeA = a.getType(false), typeB = b.getType(false);
+    if (!typeA || !typeB) return true;
+    return similarType(typeA, typeB, depth);
+  }
+
+  function similarType(a, b, depth) {
+    if (!a || depth >= 5) return b;
+    if (!a || a == b) return a;
+    if (!b) return a;
+    if (a.constructor != b.constructor) return false;
+    if (a.constructor == Arr) {
+      var innerA = a.getProp("<i>").getType(false);
+      if (!innerA) return b;
+      var innerB = b.getProp("<i>").getType(false);
+      if (!innerB || similarType(innerA, innerB, depth + 1)) return b;
+    } else if (a.constructor == Obj) {
+      var propsA = 0, propsB = 0, same = 0;
+      for (var prop in a.props) {
+        propsA++;
+        if (prop in b.props && similarAVal(a.props[prop], b.props[prop], depth + 1))
+          same++;
+      }
+      for (var prop in b.props) propsB++;
+      if (propsA && propsB && same < Math.max(propsA, propsB) / 2) return false;
+      return propsA > propsB ? a : b;
+    } else if (a.constructor == Fn) {
+      if (a.args.length != b.args.length ||
+          !a.args.every(function(tp, i) { return similarAVal(tp, b.args[i], depth + 1); }) ||
+          !similarAVal(a.retval, b.retval, depth + 1) || !similarAVal(a.self, b.self, depth + 1))
+        return false;
+      return a;
+    } else {
+      return false;
+    }
+  }
+
+  var simplifyTypes = exports.simplifyTypes = function(types) {
+    var found = [];
+    outer: for (var i = 0; i < types.length; ++i) {
+      var tp = types[i];
+      for (var j = 0; j < found.length; j++) {
+        var similar = similarType(tp, found[j], 0);
+        if (similar) {
+          found[j] = similar;
+          continue outer;
+        }
+      }
+      found.push(tp);
+    }
+    return found;
+  };
+
+  function canonicalType(types) {
+    var arrays = 0, fns = 0, objs = 0, prim = null;
+    for (var i = 0; i < types.length; ++i) {
+      var tp = types[i];
+      if (tp instanceof Arr) ++arrays;
+      else if (tp instanceof Fn) ++fns;
+      else if (tp instanceof Obj) ++objs;
+      else if (tp instanceof Prim) {
+        if (prim && tp.name != prim.name) return null;
+        prim = tp;
+      }
+    }
+    var kinds = (arrays && 1) + (fns && 1) + (objs && 1) + (prim && 1);
+    if (kinds > 1) return null;
+    if (prim) return prim;
+
+    var maxScore = 0, maxTp = null;
+    for (var i = 0; i < types.length; ++i) {
+      var tp = types[i], score = 0;
+      if (arrays) {
+        score = tp.getProp("<i>").isEmpty() ? 1 : 2;
+      } else if (fns) {
+        score = 1;
+        for (var j = 0; j < tp.args.length; ++j) if (!tp.args[j].isEmpty()) ++score;
+        if (!tp.retval.isEmpty()) ++score;
+      } else if (objs) {
+        score = tp.name ? 100 : 2;
+      }
+      if (score >= maxScore) { maxScore = score; maxTp = tp; }
+    }
+    return maxTp;
+  }
+
+  // PROPAGATION STRATEGIES
+
+  function Constraint() {}
+  Constraint.prototype = extend(ANull, {
+    init: function() { this.origin = cx.curOrigin; }
+  });
+
+  var constraint = exports.constraint = function(props, methods) {
+    var body = "this.init();";
+    props = props ? props.split(", ") : [];
+    for (var i = 0; i < props.length; ++i)
+      body += "this." + props[i] + " = " + props[i] + ";";
+    var ctor = Function.apply(null, props.concat([body]));
+    ctor.prototype = Object.create(Constraint.prototype);
+    for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m];
+    return ctor;
+  };
+
+  var PropIsSubset = constraint("prop, target", {
+    addType: function(type, weight) {
+      if (type.getProp)
+        type.getProp(this.prop).propagate(this.target, weight);
+    },
+    propHint: function() { return this.prop; },
+    propagatesTo: function() {
+      if (this.prop == "<i>" || !/[^\w_]/.test(this.prop))
+        return {target: this.target, pathExt: "." + this.prop};
+    }
+  });
+
+  var PropHasSubset = exports.PropHasSubset = constraint("prop, type, originNode", {
+    addType: function(type, weight) {
+      if (!(type instanceof Obj)) return;
+      var prop = type.defProp(this.prop, this.originNode);
+      if (!prop.origin) prop.origin = this.origin;
+      this.type.propagate(prop, weight);
+    },
+    propHint: function() { return this.prop; }
+  });
+
+  var ForAllProps = constraint("c", {
+    addType: function(type) {
+      if (!(type instanceof Obj)) return;
+      type.forAllProps(this.c);
+    }
+  });
+
+  function withDisabledComputing(fn, body) {
+    cx.disabledComputing = {fn: fn, prev: cx.disabledComputing};
+    try {
+      return body();
+    } finally {
+      cx.disabledComputing = cx.disabledComputing.prev;
+    }
+  }
+  var IsCallee = exports.IsCallee = constraint("self, args, argNodes, retval", {
+    init: function() {
+      Constraint.prototype.init.call(this);
+      this.disabled = cx.disabledComputing;
+    },
+    addType: function(fn, weight) {
+      if (!(fn instanceof Fn)) return;
+      for (var i = 0; i < this.args.length; ++i) {
+        if (i < fn.args.length) this.args[i].propagate(fn.args[i], weight);
+        if (fn.arguments) this.args[i].propagate(fn.arguments, weight);
+      }
+      this.self.propagate(fn.self, this.self == cx.topScope ? WG_GLOBAL_THIS : weight);
+      var compute = fn.computeRet;
+      if (compute) for (var d = this.disabled; d; d = d.prev)
+        if (d.fn == fn || fn.originNode && d.fn.originNode == fn.originNode) compute = null;
+      if (compute)
+        compute(this.self, this.args, this.argNodes).propagate(this.retval, weight);
+      else
+        fn.retval.propagate(this.retval, weight);
+    },
+    typeHint: function() {
+      var names = [];
+      for (var i = 0; i < this.args.length; ++i) names.push("?");
+      return new Fn(null, this.self, this.args, names, ANull);
+    },
+    propagatesTo: function() {
+      return {target: this.retval, pathExt: ".!ret"};
+    }
+  });
+
+  var HasMethodCall = constraint("propName, args, argNodes, retval", {
+    init: function() {
+      Constraint.prototype.init.call(this);
+      this.disabled = cx.disabledComputing;
+    },
+    addType: function(obj, weight) {
+      var callee = new IsCallee(obj, this.args, this.argNodes, this.retval);
+      callee.disabled = this.disabled;
+      obj.getProp(this.propName).propagate(callee, weight);
+    },
+    propHint: function() { return this.propName; }
+  });
+
+  var IsCtor = exports.IsCtor = constraint("target, noReuse", {
+    addType: function(f, weight) {
+      if (!(f instanceof Fn)) return;
+      if (cx.parent && !cx.parent.options.reuseInstances) this.noReuse = true;
+      f.getProp("prototype").propagate(new IsProto(this.noReuse ? false : f, this.target), weight);
+    }
+  });
+
+  var getInstance = exports.getInstance = function(obj, ctor) {
+    if (ctor === false) return new Obj(obj);
+
+    if (!ctor) ctor = obj.hasCtor;
+    if (!obj.instances) obj.instances = [];
+    for (var i = 0; i < obj.instances.length; ++i) {
+      var cur = obj.instances[i];
+      if (cur.ctor == ctor) return cur.instance;
+    }
+    var instance = new Obj(obj, ctor && ctor.name);
+    instance.origin = obj.origin;
+    obj.instances.push({ctor: ctor, instance: instance});
+    return instance;
+  };
+
+  var IsProto = exports.IsProto = constraint("ctor, target", {
+    addType: function(o, _weight) {
+      if (!(o instanceof Obj)) return;
+      if ((this.count = (this.count || 0) + 1) > 8) return;
+      if (o == cx.protos.Array)
+        this.target.addType(new Arr);
+      else
+        this.target.addType(getInstance(o, this.ctor));
+    }
+  });
+
+  var FnPrototype = constraint("fn", {
+    addType: function(o, _weight) {
+      if (o instanceof Obj && !o.hasCtor) {
+        o.hasCtor = this.fn;
+        var adder = new SpeculativeThis(o, this.fn);
+        adder.addType(this.fn);
+        o.forAllProps(function(_prop, val, local) {
+          if (local) val.propagate(adder);
+        });
+      }
+    }
+  });
+
+  var IsAdded = constraint("other, target", {
+    addType: function(type, weight) {
+      if (type == cx.str)
+        this.target.addType(cx.str, weight);
+      else if (type == cx.num && this.other.hasType(cx.num))
+        this.target.addType(cx.num, weight);
+    },
+    typeHint: function() { return this.other; }
+  });
+
+  var IfObj = exports.IfObj = constraint("target", {
+    addType: function(t, weight) {
+      if (t instanceof Obj) this.target.addType(t, weight);
+    },
+    propagatesTo: function() { return this.target; }
+  });
+
+  var SpeculativeThis = constraint("obj, ctor", {
+    addType: function(tp) {
+      if (tp instanceof Fn && tp.self && tp.self.isEmpty())
+        tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_THIS);
+    }
+  });
+
+  var Muffle = constraint("inner, weight", {
+    addType: function(tp, weight) {
+      this.inner.addType(tp, Math.min(weight, this.weight));
+    },
+    propagatesTo: function() { return this.inner.propagatesTo(); },
+    typeHint: function() { return this.inner.typeHint(); },
+    propHint: function() { return this.inner.propHint(); }
+  });
+
+  // TYPE OBJECTS
+
+  var Type = exports.Type = function() {};
+  Type.prototype = extend(ANull, {
+    constructor: Type,
+    propagate: function(c, w) { c.addType(this, w); },
+    hasType: function(other) { return other == this; },
+    isEmpty: function() { return false; },
+    typeHint: function() { return this; },
+    getType: function() { return this; }
+  });
+
+  var Prim = exports.Prim = function(proto, name) { this.name = name; this.proto = proto; };
+  Prim.prototype = extend(Type.prototype, {
+    constructor: Prim,
+    toString: function() { return this.name; },
+    getProp: function(prop) {return this.proto.hasProp(prop) || ANull;},
+    gatherProperties: function(f, depth) {
+      if (this.proto) this.proto.gatherProperties(f, depth);
+    }
+  });
+
+  var Obj = exports.Obj = function(proto, name) {
+    if (!this.props) this.props = Object.create(null);
+    this.proto = proto === true ? cx.protos.Object : proto;
+    if (proto && !name && proto.name && !(this instanceof Fn)) {
+      var match = /^(.*)\.prototype$/.exec(this.proto.name);
+      if (match) name = match[1];
+    }
+    this.name = name;
+    this.maybeProps = null;
+    this.origin = cx.curOrigin;
+  };
+  Obj.prototype = extend(Type.prototype, {
+    constructor: Obj,
+    toString: function(maxDepth) {
+      if (maxDepth == null) maxDepth = 0;
+      if (maxDepth <= 0 && this.name) return this.name;
+      var props = [], etc = false;
+      for (var prop in this.props) if (prop != "<i>") {
+        if (props.length > 5) { etc = true; break; }
+        if (maxDepth)
+          props.push(prop + ": " + toString(this.props[prop], maxDepth - 1, this));
+        else
+          props.push(prop);
+      }
+      props.sort();
+      if (etc) props.push("...");
+      return "{" + props.join(", ") + "}";
+    },
+    hasProp: function(prop, searchProto) {
+      var found = this.props[prop];
+      if (searchProto !== false)
+        for (var p = this.proto; p && !found; p = p.proto) found = p.props[prop];
+      return found;
+    },
+    defProp: function(prop, originNode) {
+      var found = this.hasProp(prop, false);
+      if (found) {
+        if (originNode && !found.originNode) found.originNode = originNode;
+        return found;
+      }
+      if (prop == "__proto__" || prop == "✖") return ANull;
+
+      var av = this.maybeProps && this.maybeProps[prop];
+      if (av) {
+        delete this.maybeProps[prop];
+        this.maybeUnregProtoPropHandler();
+      } else {
+        av = new AVal;
+        av.propertyOf = this;
+      }
+
+      this.props[prop] = av;
+      av.originNode = originNode;
+      av.origin = cx.curOrigin;
+      this.broadcastProp(prop, av, true);
+      return av;
+    },
+    getProp: function(prop) {
+      var found = this.hasProp(prop, true) || (this.maybeProps && this.maybeProps[prop]);
+      if (found) return found;
+      if (prop == "__proto__" || prop == "✖") return ANull;
+      var av = this.ensureMaybeProps()[prop] = new AVal;
+      av.propertyOf = this;
+      return av;
+    },
+    broadcastProp: function(prop, val, local) {
+      if (local) {
+        this.signal("addProp", prop, val);
+        // If this is a scope, it shouldn't be registered
+        if (!(this instanceof Scope)) registerProp(prop, this);
+      }
+
+      if (this.onNewProp) for (var i = 0; i < this.onNewProp.length; ++i) {
+        var h = this.onNewProp[i];
+        h.onProtoProp ? h.onProtoProp(prop, val, local) : h(prop, val, local);
+      }
+    },
+    onProtoProp: function(prop, val, _local) {
+      var maybe = this.maybeProps && this.maybeProps[prop];
+      if (maybe) {
+        delete this.maybeProps[prop];
+        this.maybeUnregProtoPropHandler();
+        this.proto.getProp(prop).propagate(maybe);
+      }
+      this.broadcastProp(prop, val, false);
+    },
+    ensureMaybeProps: function() {
+      if (!this.maybeProps) {
+        if (this.proto) this.proto.forAllProps(this);
+        this.maybeProps = Object.create(null);
+      }
+      return this.maybeProps;
+    },
+    removeProp: function(prop) {
+      var av = this.props[prop];
+      delete this.props[prop];
+      this.ensureMaybeProps()[prop] = av;
+      av.types.length = 0;
+    },
+    forAllProps: function(c) {
+      if (!this.onNewProp) {
+        this.onNewProp = [];
+        if (this.proto) this.proto.forAllProps(this);
+      }
+      this.onNewProp.push(c);
+      for (var o = this; o; o = o.proto) for (var prop in o.props) {
+        if (c.onProtoProp)
+          c.onProtoProp(prop, o.props[prop], o == this);
+        else
+          c(prop, o.props[prop], o == this);
+      }
+    },
+    maybeUnregProtoPropHandler: function() {
+      if (this.maybeProps) {
+        for (var _n in this.maybeProps) return;
+        this.maybeProps = null;
+      }
+      if (!this.proto || this.onNewProp && this.onNewProp.length) return;
+      this.proto.unregPropHandler(this);
+    },
+    unregPropHandler: function(handler) {
+      for (var i = 0; i < this.onNewProp.length; ++i)
+        if (this.onNewProp[i] == handler) { this.onNewProp.splice(i, 1); break; }
+      this.maybeUnregProtoPropHandler();
+    },
+    gatherProperties: function(f, depth) {
+      for (var prop in this.props) if (prop != "<i>")
+        f(prop, this, depth);
+      if (this.proto) this.proto.gatherProperties(f, depth + 1);
+    },
+    getObjType: function() { return this; }
+  });
+
+  var Fn = exports.Fn = function(name, self, args, argNames, retval) {
+    Obj.call(this, cx.protos.Function, name);
+    this.self = self;
+    this.args = args;
+    this.argNames = argNames;
+    this.retval = retval;
+  };
+  Fn.prototype = extend(Obj.prototype, {
+    constructor: Fn,
+    toString: function(maxDepth) {
+      if (maxDepth == null) maxDepth = 0;
+      var str = "fn(";
+      for (var i = 0; i < this.args.length; ++i) {
+        if (i) str += ", ";
+        var name = this.argNames[i];
+        if (name && name != "?") str += name + ": ";
+        str += maxDepth > -3 ? toString(this.args[i], maxDepth - 1, this) : "?";
+      }
+      str += ")";
+      if (!this.retval.isEmpty())
+        str += " -> " + (maxDepth > -3 ? toString(this.retval, maxDepth - 1, this) : "?");
+      return str;
+    },
+    getProp: function(prop) {
+      if (prop == "prototype") {
+        var known = this.hasProp(prop, false);
+        if (!known) {
+          known = this.defProp(prop);
+          var proto = new Obj(true, this.name && this.name + ".prototype");
+          proto.origin = this.origin;
+          known.addType(proto, WG_MADEUP_PROTO);
+        }
+        return known;
+      }
+      return Obj.prototype.getProp.call(this, prop);
+    },
+    defProp: function(prop, originNode) {
+      if (prop == "prototype") {
+        var found = this.hasProp(prop, false);
+        if (found) return found;
+        found = Obj.prototype.defProp.call(this, prop, originNode);
+        found.origin = this.origin;
+        found.propagate(new FnPrototype(this));
+        return found;
+      }
+      return Obj.prototype.defProp.call(this, prop, originNode);
+    },
+    getFunctionType: function() { return this; }
+  });
+
+  var Arr = exports.Arr = function(contentType) {
+    Obj.call(this, cx.protos.Array);
+    var content = this.defProp("<i>");
+    if (contentType) contentType.propagate(content);
+  };
+  Arr.prototype = extend(Obj.prototype, {
+    constructor: Arr,
+    toString: function(maxDepth) {
+      if (maxDepth == null) maxDepth = 0;
+      return "[" + (maxDepth > -3 ? toString(this.getProp("<i>"), maxDepth - 1, this) : "?") + "]";
+    }
+  });
+
+  // THE PROPERTY REGISTRY
+
+  function registerProp(prop, obj) {
+    var data = cx.props[prop] || (cx.props[prop] = []);
+    data.push(obj);
+  }
+
+  function objsWithProp(prop) {
+    return cx.props[prop];
+  }
+
+  // INFERENCE CONTEXT
+
+  exports.Context = function(defs, parent) {
+    this.parent = parent;
+    this.props = Object.create(null);
+    this.protos = Object.create(null);
+    this.origins = [];
+    this.curOrigin = "ecma5";
+    this.paths = Object.create(null);
+    this.definitions = Object.create(null);
+    this.purgeGen = 0;
+    this.workList = null;
+    this.disabledComputing = null;
+
+    exports.withContext(this, function() {
+      cx.protos.Object = new Obj(null, "Object.prototype");
+      cx.topScope = new Scope();
+      cx.topScope.name = "<top>";
+      cx.protos.Array = new Obj(true, "Array.prototype");
+      cx.protos.Function = new Obj(true, "Function.prototype");
+      cx.protos.RegExp = new Obj(true, "RegExp.prototype");
+      cx.protos.String = new Obj(true, "String.prototype");
+      cx.protos.Number = new Obj(true, "Number.prototype");
+      cx.protos.Boolean = new Obj(true, "Boolean.prototype");
+      cx.str = new Prim(cx.protos.String, "string");
+      cx.bool = new Prim(cx.protos.Boolean, "bool");
+      cx.num = new Prim(cx.protos.Number, "number");
+      cx.curOrigin = null;
+
+      if (defs) for (var i = 0; i < defs.length; ++i)
+        def.load(defs[i]);
+    });
+  };
+
+  var cx = null;
+  exports.cx = function() { return cx; };
+
+  exports.withContext = function(context, f) {
+    var old = cx;
+    cx = context;
+    try { return f(); }
+    finally { cx = old; }
+  };
+
+  exports.TimedOut = function() {
+    this.message = "Timed out";
+    this.stack = (new Error()).stack;
+  };
+  exports.TimedOut.prototype = Object.create(Error.prototype);
+  exports.TimedOut.prototype.name = "infer.TimedOut";
+
+  var timeout;
+  exports.withTimeout = function(ms, f) {
+    var end = +new Date + ms;
+    var oldEnd = timeout;
+    if (oldEnd && oldEnd < end) return f();
+    timeout = end;
+    try { return f(); }
+    finally { timeout = oldEnd; }
+  };
+
+  exports.addOrigin = function(origin) {
+    if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin);
+  };
+
+  var baseMaxWorkDepth = 20, reduceMaxWorkDepth = 0.0001;
+  function withWorklist(f) {
+    if (cx.workList) return f(cx.workList);
+
+    var list = [], depth = 0;
+    var add = cx.workList = function(type, target, weight) {
+      if (depth < baseMaxWorkDepth - reduceMaxWorkDepth * list.length)
+        list.push(type, target, weight, depth);
+    };
+    try {
+      var ret = f(add);
+      for (var i = 0; i < list.length; i += 4) {
+        if (timeout && +new Date >= timeout)
+          throw new exports.TimedOut();
+        depth = list[i + 3] + 1;
+        list[i + 1].addType(list[i], list[i + 2]);
+      }
+      return ret;
+    } finally {
+      cx.workList = null;
+    }
+  }
+
+  // SCOPES
+
+  var Scope = exports.Scope = function(prev) {
+    Obj.call(this, prev || true);
+    this.prev = prev;
+  };
+  Scope.prototype = extend(Obj.prototype, {
+    constructor: Scope,
+    defVar: function(name, originNode) {
+      for (var s = this; ; s = s.proto) {
+        var found = s.props[name];
+        if (found) return found;
+        if (!s.prev) return s.defProp(name, originNode);
+      }
+    }
+  });
+
+  // RETVAL COMPUTATION HEURISTICS
+
+  function maybeInstantiate(scope, score) {
+    if (scope.fnType)
+      scope.fnType.instantiateScore = (scope.fnType.instantiateScore || 0) + score;
+  }
+
+  var NotSmaller = {};
+  function nodeSmallerThan(node, n) {
+    try {
+      walk.simple(node, {Expression: function() { if (--n <= 0) throw NotSmaller; }});
+      return true;
+    } catch(e) {
+      if (e == NotSmaller) return false;
+      throw e;
+    }
+  }
+
+  function maybeTagAsInstantiated(node, scope) {
+    var score = scope.fnType.instantiateScore;
+    if (!cx.disabledComputing && score && scope.fnType.args.length && nodeSmallerThan(node, score * 5)) {
+      maybeInstantiate(scope.prev, score / 2);
+      setFunctionInstantiated(node, scope);
+      return true;
+    } else {
+      scope.fnType.instantiateScore = null;
+    }
+  }
+
+  function setFunctionInstantiated(node, scope) {
+    var fn = scope.fnType;
+    // Disconnect the arg avals, so that we can add info to them without side effects
+    for (var i = 0; i < fn.args.length; ++i) fn.args[i] = new AVal;
+    fn.self = new AVal;
+    fn.computeRet = function(self, args) {
+      // Prevent recursion
+      return withDisabledComputing(fn, function() {
+        var oldOrigin = cx.curOrigin;
+        cx.curOrigin = fn.origin;
+        var scopeCopy = new Scope(scope.prev);
+        scopeCopy.originNode = scope.originNode;
+        for (var v in scope.props) {
+          var local = scopeCopy.defProp(v, scope.props[v].originNode);
+          for (var i = 0; i < args.length; ++i) if (fn.argNames[i] == v && i < args.length)
+            args[i].propagate(local);
+        }
+        var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames;
+        while (argNames.length < args.length) argNames.push("?");
+        scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull);
+        scopeCopy.fnType.originNode = fn.originNode;
+        if (fn.arguments) {
+          var argset = scopeCopy.fnType.arguments = new AVal;
+          scopeCopy.defProp("arguments").addType(new Arr(argset));
+          for (var i = 0; i < args.length; ++i) args[i].propagate(argset);
+        }
+        node.body.scope = scopeCopy;
+        walk.recursive(node.body, scopeCopy, null, scopeGatherer);
+        walk.recursive(node.body, scopeCopy, null, inferWrapper);
+        cx.curOrigin = oldOrigin;
+        return scopeCopy.fnType.retval;
+      });
+    };
+  }
+
+  function maybeTagAsGeneric(scope) {
+    var fn = scope.fnType, target = fn.retval;
+    if (target == ANull) return;
+    var targetInner, asArray;
+    if (!target.isEmpty() && (targetInner = target.getType()) instanceof Arr)
+      target = asArray = targetInner.getProp("<i>");
+
+    function explore(aval, path, depth) {
+      if (depth > 3 || !aval.forward) return;
+      for (var i = 0; i < aval.forward.length; ++i) {
+        var prop = aval.forward[i].propagatesTo();
+        if (!prop) continue;
+        var newPath = path, dest;
+        if (prop instanceof AVal) {
+          dest = prop;
+        } else if (prop.target instanceof AVal) {
+          newPath += prop.pathExt;
+          dest = prop.target;
+        } else continue;
+        if (dest == target) return newPath;
+        var found = explore(dest, newPath, depth + 1);
+        if (found) return found;
+      }
+    }
+
+    var foundPath = explore(fn.self, "!this", 0);
+    for (var i = 0; !foundPath && i < fn.args.length; ++i)
+      foundPath = explore(fn.args[i], "!" + i, 0);
+
+    if (foundPath) {
+      if (asArray) foundPath = "[" + foundPath + "]";
+      var p = new def.TypeParser(foundPath);
+      var parsed = p.parseType(true);
+      fn.computeRet = parsed.apply ? parsed : function() { return parsed; };
+      fn.computeRetSource = foundPath;
+      return true;
+    }
+  }
+
+  // SCOPE GATHERING PASS
+
+  function addVar(scope, nameNode) {
+    return scope.defProp(nameNode.name, nameNode);
+  }
+
+  var scopeGatherer = walk.make({
+    Function: function(node, scope, c) {
+      var inner = node.body.scope = new Scope(scope);
+      inner.originNode = node;
+      var argVals = [], argNames = [];
+      for (var i = 0; i < node.params.length; ++i) {
+        var param = node.params[i];
+        argNames.push(param.name);
+        argVals.push(addVar(inner, param));
+      }
+      inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull);
+      inner.fnType.originNode = node;
+      if (node.id) {
+        var decl = node.type == "FunctionDeclaration";
+        addVar(decl ? scope : inner, node.id);
+      }
+      c(node.body, inner, "ScopeBody");
+    },
+    TryStatement: function(node, scope, c) {
+      c(node.block, scope, "Statement");
+      if (node.handler) {
+        var v = addVar(scope, node.handler.param);
+        c(node.handler.body, scope, "ScopeBody");
+        var e5 = cx.definitions.ecma5;
+        if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR);
+      }
+      if (node.finalizer) c(node.finalizer, scope, "Statement");
+    },
+    VariableDeclaration: function(node, scope, c) {
+      for (var i = 0; i < node.declarations.length; ++i) {
+        var decl = node.declarations[i];
+        addVar(scope, decl.id);
+        if (decl.init) c(decl.init, scope, "Expression");
+      }
+    }
+  });
+
+  // CONSTRAINT GATHERING PASS
+
+  function propName(node, scope, c) {
+    var prop = node.property;
+    if (!node.computed) return prop.name;
+    if (prop.type == "Literal" && typeof prop.value == "string") return prop.value;
+    if (c) infer(prop, scope, c, ANull);
+    return "<i>";
+  }
+
+  function unopResultType(op) {
+    switch (op) {
+    case "+": case "-": case "~": return cx.num;
+    case "!": return cx.bool;
+    case "typeof": return cx.str;
+    case "void": case "delete": return ANull;
+    }
+  }
+  function binopIsBoolean(op) {
+    switch (op) {
+    case "==": case "!=": case "===": case "!==": case "<": case ">": case ">=": case "<=":
+    case "in": case "instanceof": return true;
+    }
+  }
+  function literalType(node) {
+    if (node.regex) return getInstance(cx.protos.RegExp);
+    switch (typeof node.value) {
+    case "boolean": return cx.bool;
+    case "number": return cx.num;
+    case "string": return cx.str;
+    case "object":
+    case "function":
+      if (!node.value) return ANull;
+      return getInstance(cx.protos.RegExp);
+    }
+  }
+
+  function ret(f) {
+    return function(node, scope, c, out, name) {
+      var r = f(node, scope, c, name);
+      if (out) r.propagate(out);
+      return r;
+    };
+  }
+  function fill(f) {
+    return function(node, scope, c, out, name) {
+      if (!out) out = new AVal;
+      f(node, scope, c, out, name);
+      return out;
+    };
+  }
+
+  var inferExprVisitor = {
+    ArrayExpression: ret(function(node, scope, c) {
+      var eltval = new AVal;
+      for (var i = 0; i < node.elements.length; ++i) {
+        var elt = node.elements[i];
+        if (elt) infer(elt, scope, c, eltval);
+      }
+      return new Arr(eltval);
+    }),
+    ObjectExpression: ret(function(node, scope, c, name) {
+      var obj = node.objType = new Obj(true, name);
+      obj.originNode = node;
+
+      for (var i = 0; i < node.properties.length; ++i) {
+        var prop = node.properties[i], key = prop.key, name;
+        if (prop.value.name == "✖") continue;
+
+        if (key.type == "Identifier") {
+          name = key.name;
+        } else if (typeof key.value == "string") {
+          name = key.value;
+        }
+        if (!name || prop.kind == "set") {
+          infer(prop.value, scope, c, ANull);
+          continue;
+        }
+
+        var val = obj.defProp(name, key), out = val;
+        val.initializer = true;
+        if (prop.kind == "get")
+          out = new IsCallee(obj, [], null, val);
+        infer(prop.value, scope, c, out, name);
+      }
+      return obj;
+    }),
+    FunctionExpression: ret(function(node, scope, c, name) {
+      var inner = node.body.scope, fn = inner.fnType;
+      if (name && !fn.name) fn.name = name;
+      c(node.body, scope, "ScopeBody");
+      maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
+      if (node.id) inner.getProp(node.id.name).addType(fn);
+      return fn;
+    }),
+    SequenceExpression: ret(function(node, scope, c) {
+      for (var i = 0, l = node.expressions.length - 1; i < l; ++i)
+        infer(node.expressions[i], scope, c, ANull);
+      return infer(node.expressions[l], scope, c);
+    }),
+    UnaryExpression: ret(function(node, scope, c) {
+      infer(node.argument, scope, c, ANull);
+      return unopResultType(node.operator);
+    }),
+    UpdateExpression: ret(function(node, scope, c) {
+      infer(node.argument, scope, c, ANull);
+      return cx.num;
+    }),
+    BinaryExpression: ret(function(node, scope, c) {
+      if (node.operator == "+") {
+        var lhs = infer(node.left, scope, c);
+        var rhs = infer(node.right, scope, c);
+        if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
+        if (lhs.hasType(cx.num) && rhs.hasType(cx.num)) return cx.num;
+        var result = new AVal;
+        lhs.propagate(new IsAdded(rhs, result));
+        rhs.propagate(new IsAdded(lhs, result));
+        return result;
+      } else {
+        infer(node.left, scope, c, ANull);
+        infer(node.right, scope, c, ANull);
+        return binopIsBoolean(node.operator) ? cx.bool : cx.num;
+      }
+    }),
+    AssignmentExpression: ret(function(node, scope, c) {
+      var rhs, name, pName;
+      if (node.left.type == "MemberExpression") {
+        pName = propName(node.left, scope, c);
+        if (node.left.object.type == "Identifier")
+          name = node.left.object.name + "." + pName;
+      } else {
+        name = node.left.name;
+      }
+
+      if (node.operator != "=" && node.operator != "+=") {
+        infer(node.right, scope, c, ANull);
+        rhs = cx.num;
+      } else {
+        rhs = infer(node.right, scope, c, null, name);
+      }
+
+      if (node.left.type == "MemberExpression") {
+        var obj = infer(node.left.object, scope, c);
+        if (pName == "prototype") maybeInstantiate(scope, 20);
+        if (pName == "<i>") {
+          // This is a hack to recognize for/in loops that copy
+          // properties, and do the copying ourselves, insofar as we
+          // manage, because such loops tend to be relevant for type
+          // information.
+          var v = node.left.property.name, local = scope.props[v], over = local && local.iteratesOver;
+          if (over) {
+            maybeInstantiate(scope, 20);
+            var fromRight = node.right.type == "MemberExpression" && node.right.computed && node.right.property.name == v;
+            over.forAllProps(function(prop, val, local) {
+              if (local && prop != "prototype" && prop != "<i>")
+                obj.propagate(new PropHasSubset(prop, fromRight ? val : ANull));
+            });
+            return rhs;
+          }
+        }
+        obj.propagate(new PropHasSubset(pName, rhs, node.left.property));
+      } else { // Identifier
+        rhs.propagate(scope.defVar(node.left.name, node.left));
+      }
+      return rhs;
+    }),
+    LogicalExpression: fill(function(node, scope, c, out) {
+      infer(node.left, scope, c, out);
+      infer(node.right, scope, c, out);
+    }),
+    ConditionalExpression: fill(function(node, scope, c, out) {
+      infer(node.test, scope, c, ANull);
+      infer(node.consequent, scope, c, out);
+      infer(node.alternate, scope, c, out);
+    }),
+    NewExpression: fill(function(node, scope, c, out, name) {
+      if (node.callee.type == "Identifier" && node.callee.name in scope.props)
+        maybeInstantiate(scope, 20);
+
+      for (var i = 0, args = []; i < node.arguments.length; ++i)
+        args.push(infer(node.arguments[i], scope, c));
+      var callee = infer(node.callee, scope, c);
+      var self = new AVal;
+      callee.propagate(new IsCtor(self, name && /\.prototype$/.test(name)));
+      self.propagate(out, WG_NEW_INSTANCE);
+      callee.propagate(new IsCallee(self, args, node.arguments, new IfObj(out)));
+    }),
+    CallExpression: fill(function(node, scope, c, out) {
+      for (var i = 0, args = []; i < node.arguments.length; ++i)
+        args.push(infer(node.arguments[i], scope, c));
+      if (node.callee.type == "MemberExpression") {
+        var self = infer(node.callee.object, scope, c);
+        var pName = propName(node.callee, scope, c);
+        if ((pName == "call" || pName == "apply") &&
+            scope.fnType && scope.fnType.args.indexOf(self) > -1)
+          maybeInstantiate(scope, 30);
+        self.propagate(new HasMethodCall(pName, args, node.arguments, out));
+      } else {
+        var callee = infer(node.callee, scope, c);
+        if (scope.fnType && scope.fnType.args.indexOf(callee) > -1)
+          maybeInstantiate(scope, 30);
+        var knownFn = callee.getFunctionType();
+        if (knownFn && knownFn.instantiateScore && scope.fnType)
+          maybeInstantiate(scope, knownFn.instantiateScore / 5);
+        callee.propagate(new IsCallee(cx.topScope, args, node.arguments, out));
+      }
+    }),
+    MemberExpression: fill(function(node, scope, c, out) {
+      var name = propName(node, scope);
+      var obj = infer(node.object, scope, c);
+      var prop = obj.getProp(name);
+      if (name == "<i>") {
+        var propType = infer(node.property, scope, c);
+        if (!propType.hasType(cx.num))
+          return prop.propagate(out, WG_MULTI_MEMBER);
+      }
+      prop.propagate(out);
+    }),
+    Identifier: ret(function(node, scope) {
+      if (node.name == "arguments" && scope.fnType && !(node.name in scope.props))
+        scope.defProp(node.name, scope.fnType.originNode)
+          .addType(new Arr(scope.fnType.arguments = new AVal));
+      return scope.getProp(node.name);
+    }),
+    ThisExpression: ret(function(_node, scope) {
+      return scope.fnType ? scope.fnType.self : cx.topScope;
+    }),
+    Literal: ret(function(node) {
+      return literalType(node);
+    })
+  };
+
+  function infer(node, scope, c, out, name) {
+    return inferExprVisitor[node.type](node, scope, c, out, name);
+  }
+
+  var inferWrapper = walk.make({
+    Expression: function(node, scope, c) {
+      infer(node, scope, c, ANull);
+    },
+
+    FunctionDeclaration: function(node, scope, c) {
+      var inner = node.body.scope, fn = inner.fnType;
+      c(node.body, scope, "ScopeBody");
+      maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
+      var prop = scope.getProp(node.id.name);
+      prop.addType(fn);
+    },
+
+    VariableDeclaration: function(node, scope, c) {
+      for (var i = 0; i < node.declarations.length; ++i) {
+        var decl = node.declarations[i], prop = scope.getProp(decl.id.name);
+        if (decl.init)
+          infer(decl.init, scope, c, prop, decl.id.name);
+      }
+    },
+
+    ReturnStatement: function(node, scope, c) {
+      if (!node.argument) return;
+      var output = ANull;
+      if (scope.fnType) {
+        if (scope.fnType.retval == ANull) scope.fnType.retval = new AVal;
+        output = scope.fnType.retval;
+      }
+      infer(node.argument, scope, c, output);
+    },
+
+    ForInStatement: function(node, scope, c) {
+      var source = infer(node.right, scope, c);
+      if ((node.right.type == "Identifier" && node.right.name in scope.props) ||
+          (node.right.type == "MemberExpression" && node.right.property.name == "prototype")) {
+        maybeInstantiate(scope, 5);
+        var varName;
+        if (node.left.type == "Identifier") {
+          varName = node.left.name;
+        } else if (node.left.type == "VariableDeclaration") {
+          varName = node.left.declarations[0].id.name;
+        }
+        if (varName && varName in scope.props)
+          scope.getProp(varName).iteratesOver = source;
+      }
+      c(node.body, scope, "Statement");
+    },
+
+    ScopeBody: function(node, scope, c) { c(node, node.scope || scope); }
+  });
+
+  // PARSING
+
+  function runPasses(passes, pass) {
+    var arr = passes && passes[pass];
+    var args = Array.prototype.slice.call(arguments, 2);
+    if (arr) for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+  }
+
+  var parse = exports.parse = function(text, passes, options) {
+    var ast;
+    try { ast = acorn.parse(text, options); }
+    catch(e) { ast = acorn_loose.parse_dammit(text, options); }
+    runPasses(passes, "postParse", ast, text);
+    return ast;
+  };
+
+  // ANALYSIS INTERFACE
+
+  exports.analyze = function(ast, name, scope, passes) {
+    if (typeof ast == "string") ast = parse(ast);
+
+    if (!name) name = "file#" + cx.origins.length;
+    exports.addOrigin(cx.curOrigin = name);
+
+    if (!scope) scope = cx.topScope;
+    walk.recursive(ast, scope, null, scopeGatherer);
+    runPasses(passes, "preInfer", ast, scope);
+    walk.recursive(ast, scope, null, inferWrapper);
+    runPasses(passes, "postInfer", ast, scope);
+
+    cx.curOrigin = null;
+  };
+
+  // PURGING
+
+  exports.purge = function(origins, start, end) {
+    var test = makePredicate(origins, start, end);
+    ++cx.purgeGen;
+    cx.topScope.purge(test);
+    for (var prop in cx.props) {
+      var list = cx.props[prop];
+      for (var i = 0; i < list.length; ++i) {
+        var obj = list[i], av = obj.props[prop];
+        if (!av || test(av, av.originNode)) list.splice(i--, 1);
+      }
+      if (!list.length) delete cx.props[prop];
+    }
+  };
+
+  function makePredicate(origins, start, end) {
+    var arr = Array.isArray(origins);
+    if (arr && origins.length == 1) { origins = origins[0]; arr = false; }
+    if (arr) {
+      if (end == null) return function(n) { return origins.indexOf(n.origin) > -1; };
+      return function(n, pos) { return pos && pos.start >= start && pos.end <= end && origins.indexOf(n.origin) > -1; };
+    } else {
+      if (end == null) return function(n) { return n.origin == origins; };
+      return function(n, pos) { return pos && pos.start >= start && pos.end <= end && n.origin == origins; };
+    }
+  }
+
+  AVal.prototype.purge = function(test) {
+    if (this.purgeGen == cx.purgeGen) return;
+    this.purgeGen = cx.purgeGen;
+    for (var i = 0; i < this.types.length; ++i) {
+      var type = this.types[i];
+      if (test(type, type.originNode))
+        this.types.splice(i--, 1);
+      else
+        type.purge(test);
+    }
+    if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
+      var f = this.forward[i];
+      if (test(f)) {
+        this.forward.splice(i--, 1);
+        if (this.props) this.props = null;
+      } else if (f.purge) {
+        f.purge(test);
+      }
+    }
+  };
+  ANull.purge = function() {};
+  Obj.prototype.purge = function(test) {
+    if (this.purgeGen == cx.purgeGen) return true;
+    this.purgeGen = cx.purgeGen;
+    for (var p in this.props) {
+      var av = this.props[p];
+      if (test(av, av.originNode))
+        this.removeProp(p);
+      av.purge(test);
+    }
+  };
+  Fn.prototype.purge = function(test) {
+    if (Obj.prototype.purge.call(this, test)) return;
+    this.self.purge(test);
+    this.retval.purge(test);
+    for (var i = 0; i < this.args.length; ++i) this.args[i].purge(test);
+  };
+
+  // EXPRESSION TYPE DETERMINATION
+
+  function findByPropertyName(name) {
+    guessing = true;
+    var found = objsWithProp(name);
+    if (found) for (var i = 0; i < found.length; ++i) {
+      var val = found[i].getProp(name);
+      if (!val.isEmpty()) return val;
+    }
+    return ANull;
+  }
+
+  var typeFinder = {
+    ArrayExpression: function(node, scope) {
+      var eltval = new AVal;
+      for (var i = 0; i < node.elements.length; ++i) {
+        var elt = node.elements[i];
+        if (elt) findType(elt, scope).propagate(eltval);
+      }
+      return new Arr(eltval);
+    },
+    ObjectExpression: function(node) {
+      return node.objType;
+    },
+    FunctionExpression: function(node) {
+      return node.body.scope.fnType;
+    },
+    SequenceExpression: function(node, scope) {
+      return findType(node.expressions[node.expressions.length-1], scope);
+    },
+    UnaryExpression: function(node) {
+      return unopResultType(node.operator);
+    },
+    UpdateExpression: function() {
+      return cx.num;
+    },
+    BinaryExpression: function(node, scope) {
+      if (binopIsBoolean(node.operator)) return cx.bool;
+      if (node.operator == "+") {
+        var lhs = findType(node.left, scope);
+        var rhs = findType(node.right, scope);
+        if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
+      }
+      return cx.num;
+    },
+    AssignmentExpression: function(node, scope) {
+      return findType(node.right, scope);
+    },
+    LogicalExpression: function(node, scope) {
+      var lhs = findType(node.left, scope);
+      return lhs.isEmpty() ? findType(node.right, scope) : lhs;
+    },
+    ConditionalExpression: function(node, scope) {
+      var lhs = findType(node.consequent, scope);
+      return lhs.isEmpty() ? findType(node.alternate, scope) : lhs;
+    },
+    NewExpression: function(node, scope) {
+      var f = findType(node.callee, scope).getFunctionType();
+      var proto = f && f.getProp("prototype").getObjType();
+      if (!proto) return ANull;
+      return getInstance(proto, f);
+    },
+    CallExpression: function(node, scope) {
+      var f = findType(node.callee, scope).getFunctionType();
+      if (!f) return ANull;
+      if (f.computeRet) {
+        for (var i = 0, args = []; i < node.arguments.length; ++i)
+          args.push(findType(node.arguments[i], scope));
+        var self = ANull;
+        if (node.callee.type == "MemberExpression")
+          self = findType(node.callee.object, scope);
+        return f.computeRet(self, args, node.arguments);
+      } else {
+        return f.retval;
+      }
+    },
+    MemberExpression: function(node, scope) {
+      var propN = propName(node, scope), obj = findType(node.object, scope).getType();
+      if (obj) return obj.getProp(propN);
+      if (propN == "<i>") return ANull;
+      return findByPropertyName(propN);
+    },
+    Identifier: function(node, scope) {
+      return scope.hasProp(node.name) || ANull;
+    },
+    ThisExpression: function(_node, scope) {
+      return scope.fnType ? scope.fnType.self : cx.topScope;
+    },
+    Literal: function(node) {
+      return literalType(node);
+    }
+  };
+
+  function findType(node, scope) {
+    return typeFinder[node.type](node, scope);
+  }
+
+  var searchVisitor = exports.searchVisitor = walk.make({
+    Function: function(node, _st, c) {
+      var scope = node.body.scope;
+      if (node.id) c(node.id, scope);
+      for (var i = 0; i < node.params.length; ++i)
+        c(node.params[i], scope);
+      c(node.body, scope, "ScopeBody");
+    },
+    TryStatement: function(node, st, c) {
+      if (node.handler)
+        c(node.handler.param, st);
+      walk.base.TryStatement(node, st, c);
+    },
+    VariableDeclaration: function(node, st, c) {
+      for (var i = 0; i < node.declarations.length; ++i) {
+        var decl = node.declarations[i];
+        c(decl.id, st);
+        if (decl.init) c(decl.init, st, "Expression");
+      }
+    }
+  });
+  exports.fullVisitor = walk.make({
+    MemberExpression: function(node, st, c) {
+      c(node.object, st, "Expression");
+      c(node.property, st, node.computed ? "Expression" : null);
+    },
+    ObjectExpression: function(node, st, c) {
+      for (var i = 0; i < node.properties.length; ++i) {
+        c(node.properties[i].value, st, "Expression");
+        c(node.properties[i].key, st);
+      }
+    }
+  }, searchVisitor);
+
+  exports.findExpressionAt = function(ast, start, end, defaultScope, filter) {
+    var test = filter || function(_t, node) {
+      if (node.type == "Identifier" && node.name == "✖") return false;
+      return typeFinder.hasOwnProperty(node.type);
+    };
+    return walk.findNodeAt(ast, start, end, test, searchVisitor, defaultScope || cx.topScope);
+  };
+
+  exports.findExpressionAround = function(ast, start, end, defaultScope, filter) {
+    var test = filter || function(_t, node) {
+      if (start != null && node.start > start) return false;
+      if (node.type == "Identifier" && node.name == "✖") return false;
+      return typeFinder.hasOwnProperty(node.type);
+    };
+    return walk.findNodeAround(ast, end, test, searchVisitor, defaultScope || cx.topScope);
+  };
+
+  exports.expressionType = function(found) {
+    return findType(found.node, found.state);
+  };
+
+  // Finding the expected type of something, from context
+
+  exports.parentNode = function(child, ast) {
+    var stack = [];
+    function c(node, st, override) {
+      if (node.start <= child.start && node.end >= child.end) {
+        var top = stack[stack.length - 1];
+        if (node == child) throw {found: top};
+        if (top != node) stack.push(node);
+        walk.base[override || node.type](node, st, c);
+        if (top != node) stack.pop();
+      }
+    }
+    try {
+      c(ast, null);
+    } catch (e) {
+      if (e.found) return e.found;
+      throw e;
+    }
+  };
+
+  var findTypeFromContext = {
+    ArrayExpression: function(parent, _, get) { return get(parent, true).getProp("<i>"); },
+    ObjectExpression: function(parent, node, get) {
+      for (var i = 0; i < parent.properties.length; ++i) {
+        var prop = node.properties[i];
+        if (prop.value == node)
+          return get(parent, true).getProp(prop.key.name);
+      }
+    },
+    UnaryExpression: function(parent) { return unopResultType(parent.operator); },
+    UpdateExpression: function() { return cx.num; },
+    BinaryExpression: function(parent) { return binopIsBoolean(parent.operator) ? cx.bool : cx.num; },
+    AssignmentExpression: function(parent, _, get) { return get(parent.left); },
+    LogicalExpression: function(parent, _, get) { return get(parent, true); },
+    ConditionalExpression: function(parent, node, get) {
+      if (parent.consequent == node || parent.alternate == node) return get(parent, true);
+    },
+    NewExpression: function(parent, node, get) {
+      return this.CallExpression(parent, node, get);
+    },
+    CallExpression: function(parent, node, get) {
+      for (var i = 0; i < parent.arguments.length; i++) {
+        var arg = parent.arguments[i];
+        if (arg == node) {
+          var calleeType = get(parent.callee).getFunctionType();
+          if (calleeType instanceof Fn)
+            return calleeType.args[i];
+          break;
+        }
+      }
+    },
+    ReturnStatement: function(_parent, node, get) {
+      var fnNode = walk.findNodeAround(node.sourceFile.ast, node.start, "Function");
+      if (fnNode) {
+        var fnType = fnNode.node.type == "FunctionExpression"
+          ? get(fnNode.node, true).getFunctionType()
+          : fnNode.node.body.scope.fnType;
+        if (fnType) return fnType.retval.getType();
+      }
+    },
+    VariableDeclaration: function(parent, node, get) {
+      for (var i = 0; i < parent.declarations.length; i++) {
+        var decl = parent.declarations[i];
+        if (decl.init == node) return get(decl.id);
+      }
+    }
+  };
+
+  exports.typeFromContext = function(ast, found) {
+    var parent = exports.parentNode(found.node, ast);
+    var type = null;
+    if (findTypeFromContext.hasOwnProperty(parent.type)) {
+      type = findTypeFromContext[parent.type](parent, found.node, function(node, fromContext) {
+        var obj = {node: node, state: found.state};
+        var tp = fromContext ? exports.typeFromContext(ast, obj) : exports.expressionType(obj);
+        return tp || ANull;
+      });
+    }
+    return type || exports.expressionType(found);
+  };
+
+  // Flag used to indicate that some wild guessing was used to produce
+  // a type or set of completions.
+  var guessing = false;
+
+  exports.resetGuessing = function(val) { guessing = val; };
+  exports.didGuess = function() { return guessing; };
+
+  exports.forAllPropertiesOf = function(type, f) {
+    type.gatherProperties(f, 0);
+  };
+
+  var refFindWalker = walk.make({}, searchVisitor);
+
+  exports.findRefs = function(ast, baseScope, name, refScope, f) {
+    refFindWalker.Identifier = function(node, scope) {
+      if (node.name != name) return;
+      for (var s = scope; s; s = s.prev) {
+        if (s == refScope) f(node, scope);
+        if (name in s.props) return;
+      }
+    };
+    walk.recursive(ast, baseScope, null, refFindWalker);
+  };
+
+  var simpleWalker = walk.make({
+    Function: function(node, _st, c) { c(node.body, node.body.scope, "ScopeBody"); }
+  });
+
+  exports.findPropRefs = function(ast, scope, objType, propName, f) {
+    walk.simple(ast, {
+      MemberExpression: function(node, scope) {
+        if (node.computed || node.property.name != propName) return;
+        if (findType(node.object, scope).getType() == objType) f(node.property);
+      },
+      ObjectExpression: function(node, scope) {
+        if (findType(node, scope).getType() != objType) return;
+        for (var i = 0; i < node.properties.length; ++i)
+          if (node.properties[i].key.name == propName) f(node.properties[i].key);
+      }
+    }, simpleWalker, scope);
+  };
+
+  // LOCAL-VARIABLE QUERIES
+
+  var scopeAt = exports.scopeAt = function(ast, pos, defaultScope) {
+    var found = walk.findNodeAround(ast, pos, function(tp, node) {
+      return tp == "ScopeBody" && node.scope;
+    });
+    if (found) return found.node.scope;
+    else return defaultScope || cx.topScope;
+  };
+
+  exports.forAllLocalsAt = function(ast, pos, defaultScope, f) {
+    var scope = scopeAt(ast, pos, defaultScope);
+    scope.gatherProperties(f, 0);
+  };
+
+  // INIT DEF MODULE
+
+  // Delayed initialization because of cyclic dependencies.
+  def = exports.def = def.init({}, exports);
+});

+ 80 - 0
editor/js/libs/ternjs/polyfill.js

@@ -0,0 +1,80 @@
+// Shims to fill in enough of ECMAScript 5 to make Tern run. Does not
+// supply standard-compliant methods, in that some functionality is
+// left out (such as second argument to Object.create, self args in
+// array methods, etc). WILL clash with other ECMA5 polyfills in a
+// probably disruptive way.
+
+(function() {
+  Object.create = Object.create || (function() {
+    if (!({__proto__: null} instanceof Object))
+      return function(base) { return {__proto__: base}; };
+    function ctor() {}
+    var frame = document.body.appendChild(document.createElement("iframe"));
+    frame.src = "javascript:";
+    var empty = frame.contentWindow.Object.prototype;
+    delete empty.hasOwnProperty;
+    delete empty.isPrototypeOf;
+    delete empty.propertyIsEnumerable;
+    delete empty.valueOf;
+    delete empty.toString;
+    delete empty.toLocaleString;
+    delete empty.constructor;
+    return function(base) { ctor.prototype = base || empty; return new ctor(); };
+  })();
+
+  // Array methods
+
+  var AP = Array.prototype;
+
+  AP.some = AP.some || function(pred) {
+    for (var i = 0; i < this.length; ++i) if (pred(this[i], i)) return true;
+  };
+
+  AP.forEach = AP.forEach || function(f) {
+    for (var i = 0; i < this.length; ++i) f(this[i], i);
+  };
+
+  AP.indexOf = AP.indexOf || function(x, start) {
+    for (var i = start || 0; i < this.length; ++i) if (this[i] === x) return i;
+    return -1;
+  };
+
+  AP.lastIndexOf = AP.lastIndexOf || function(x, start) {
+    for (var i = start == null ? this.length - 1 : start; i >= 0; ++i) if (this[i] === x) return i;
+    return -1;
+  };
+
+  AP.map = AP.map || function(f) {
+    for (var r = [], i = 0; i < this.length; ++i) r.push(f(this[i], i));
+    return r;
+  };
+
+  Array.isArray = Array.isArray || function(v) {
+    return Object.prototype.toString.call(v) == "[object Array]";
+  };
+
+  String.prototype.trim = String.prototype.trim || function() {
+    var from = 0, to = this.length;
+    while (/\s/.test(this.charAt(from))) ++from;
+    while (/\s/.test(this.charAt(to - 1))) --to;
+    return this.slice(from, to);
+  };
+
+/*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */
+if (!window.JSON) (function(){var e=void 0,i=!0,k=null,l={}.toString,m,n,p="function"===typeof define&&define.c,q=!p&&"object"==typeof exports&&exports;q||p?"object"==typeof JSON&&JSON?p?q=JSON:(q.stringify=JSON.stringify,q.parse=JSON.parse):p&&(q=this.JSON={}):q=this.JSON||(this.JSON={});var r,t,u,x,z,B,C,D,E,F,G,H,I,J=new Date(-3509827334573292),K,O,P;try{J=-109252==J.getUTCFullYear()&&0===J.getUTCMonth()&&1==J.getUTCDate()&&10==J.getUTCHours()&&37==J.getUTCMinutes()&&6==J.getUTCSeconds()&&708==J.getUTCMilliseconds()}catch(Q){}
+function R(b){var c,a,d,j=b=="json";if(j||b=="json-stringify"||b=="json-parse"){if(b=="json-stringify"||j){if(c=typeof q.stringify=="function"&&J){(d=function(){return 1}).toJSON=d;try{c=q.stringify(0)==="0"&&q.stringify(new Number)==="0"&&q.stringify(new String)=='""'&&q.stringify(l)===e&&q.stringify(e)===e&&q.stringify()===e&&q.stringify(d)==="1"&&q.stringify([d])=="[1]"&&q.stringify([e])=="[null]"&&q.stringify(k)=="null"&&q.stringify([e,l,k])=="[null,null,null]"&&q.stringify({A:[d,i,false,k,"\x00\u0008\n\u000c\r\t"]})==
+'{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'&&q.stringify(k,d)==="1"&&q.stringify([1,2],k,1)=="[\n 1,\n 2\n]"&&q.stringify(new Date(-864E13))=='"-271821-04-20T00:00:00.000Z"'&&q.stringify(new Date(864E13))=='"+275760-09-13T00:00:00.000Z"'&&q.stringify(new Date(-621987552E5))=='"-000001-01-01T00:00:00.000Z"'&&q.stringify(new Date(-1))=='"1969-12-31T23:59:59.999Z"'}catch(f){c=false}}if(!j)return c}if(b=="json-parse"||j){if(typeof q.parse=="function")try{if(q.parse("0")===0&&!q.parse(false)){d=
+q.parse('{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}');if(a=d.a.length==5&&d.a[0]==1){try{a=!q.parse('"\t"')}catch(o){}if(a)try{a=q.parse("01")!=1}catch(g){}}}}catch(h){a=false}if(!j)return a}return c&&a}}
+if(!R("json")){J||(K=Math.floor,O=[0,31,59,90,120,151,181,212,243,273,304,334],P=function(b,c){return O[c]+365*(b-1970)+K((b-1969+(c=+(c>1)))/4)-K((b-1901+c)/100)+K((b-1601+c)/400)});if(!(m={}.hasOwnProperty))m=function(b){var c={},a;if((c.__proto__=k,c.__proto__={toString:1},c).toString!=l)m=function(a){var b=this.__proto__,a=a in(this.__proto__=k,this);this.__proto__=b;return a};else{a=c.constructor;m=function(b){var c=(this.constructor||a).prototype;return b in this&&!(b in c&&this[b]===c[b])}}c=
+k;return m.call(this,b)};n=function(b,c){var a=0,d,j,f;(d=function(){this.valueOf=0}).prototype.valueOf=0;j=new d;for(f in j)m.call(j,f)&&a++;d=j=k;if(a)a=a==2?function(a,b){var c={},d=l.call(a)=="[object Function]",f;for(f in a)!(d&&f=="prototype")&&!m.call(c,f)&&(c[f]=1)&&m.call(a,f)&&b(f)}:function(a,b){var c=l.call(a)=="[object Function]",d,f;for(d in a)!(c&&d=="prototype")&&m.call(a,d)&&!(f=d==="constructor")&&b(d);(f||m.call(a,d="constructor"))&&b(d)};else{j=["valueOf","toString","toLocaleString",
+"propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];a=function(a,b){var c=l.call(a)=="[object Function]",d;for(d in a)!(c&&d=="prototype")&&m.call(a,d)&&b(d);for(c=j.length;d=j[--c];m.call(a,d)&&b(d));}}a(b,c)};R("json-stringify")||(r={"\\":"\\\\",'"':'\\"',"\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t"},t=function(b,c){return("000000"+(c||0)).slice(-b)},u=function(b){for(var c='"',a=0,d;d=b.charAt(a);a++)c=c+('\\"\u0008\u000c\n\r\t'.indexOf(d)>-1?r[d]:r[d]=d<" "?
+"\\u00"+t(2,d.charCodeAt(0).toString(16)):d);return c+'"'},x=function(b,c,a,d,j,f,o){var g=c[b],h,s,v,w,L,M,N,y,A;if(typeof g=="object"&&g){h=l.call(g);if(h=="[object Date]"&&!m.call(g,"toJSON"))if(g>-1/0&&g<1/0){if(P){v=K(g/864E5);for(h=K(v/365.2425)+1970-1;P(h+1,0)<=v;h++);for(s=K((v-P(h,0))/30.42);P(h,s+1)<=v;s++);v=1+v-P(h,s);w=(g%864E5+864E5)%864E5;L=K(w/36E5)%24;M=K(w/6E4)%60;N=K(w/1E3)%60;w=w%1E3}else{h=g.getUTCFullYear();s=g.getUTCMonth();v=g.getUTCDate();L=g.getUTCHours();M=g.getUTCMinutes();
+N=g.getUTCSeconds();w=g.getUTCMilliseconds()}g=(h<=0||h>=1E4?(h<0?"-":"+")+t(6,h<0?-h:h):t(4,h))+"-"+t(2,s+1)+"-"+t(2,v)+"T"+t(2,L)+":"+t(2,M)+":"+t(2,N)+"."+t(3,w)+"Z"}else g=k;else if(typeof g.toJSON=="function"&&(h!="[object Number]"&&h!="[object String]"&&h!="[object Array]"||m.call(g,"toJSON")))g=g.toJSON(b)}a&&(g=a.call(c,b,g));if(g===k)return"null";h=l.call(g);if(h=="[object Boolean]")return""+g;if(h=="[object Number]")return g>-1/0&&g<1/0?""+g:"null";if(h=="[object String]")return u(g);if(typeof g==
+"object"){for(b=o.length;b--;)if(o[b]===g)throw TypeError();o.push(g);y=[];c=f;f=f+j;if(h=="[object Array]"){s=0;for(b=g.length;s<b;A||(A=i),s++){h=x(s,g,a,d,j,f,o);y.push(h===e?"null":h)}b=A?j?"[\n"+f+y.join(",\n"+f)+"\n"+c+"]":"["+y.join(",")+"]":"[]"}else{n(d||g,function(b){var c=x(b,g,a,d,j,f,o);c!==e&&y.push(u(b)+":"+(j?" ":"")+c);A||(A=i)});b=A?j?"{\n"+f+y.join(",\n"+f)+"\n"+c+"}":"{"+y.join(",")+"}":"{}"}o.pop();return b}},q.stringify=function(b,c,a){var d,j,f,o,g,h;if(typeof c=="function"||
+typeof c=="object"&&c)if(l.call(c)=="[object Function]")j=c;else if(l.call(c)=="[object Array]"){f={};o=0;for(g=c.length;o<g;h=c[o++],(l.call(h)=="[object String]"||l.call(h)=="[object Number]")&&(f[h]=1));}if(a)if(l.call(a)=="[object Number]"){if((a=a-a%1)>0){d="";for(a>10&&(a=10);d.length<a;d=d+" ");}}else l.call(a)=="[object String]"&&(d=a.length<=10?a:a.slice(0,10));return x("",(h={},h[""]=b,h),j,f,d,"",[])});R("json-parse")||(z=String.fromCharCode,B={"\\":"\\",'"':'"',"/":"/",b:"\u0008",t:"\t",
+n:"\n",f:"\u000c",r:"\r"},C=function(){H=I=k;throw SyntaxError();},D=function(){for(var b=I,c=b.length,a,d,j,f,o;H<c;){a=b.charAt(H);if("\t\r\n ".indexOf(a)>-1)H++;else{if("{}[]:,".indexOf(a)>-1){H++;return a}if(a=='"'){d="@";for(H++;H<c;){a=b.charAt(H);if(a<" ")C();else if(a=="\\"){a=b.charAt(++H);if('\\"/btnfr'.indexOf(a)>-1){d=d+B[a];H++}else if(a=="u"){j=++H;for(f=H+4;H<f;H++){a=b.charAt(H);a>="0"&&a<="9"||a>="a"&&a<="f"||a>="A"&&a<="F"||C()}d=d+z("0x"+b.slice(j,H))}else C()}else{if(a=='"')break;
+d=d+a;H++}}if(b.charAt(H)=='"'){H++;return d}}else{j=H;if(a=="-"){o=i;a=b.charAt(++H)}if(a>="0"&&a<="9"){for(a=="0"&&(a=b.charAt(H+1),a>="0"&&a<="9")&&C();H<c&&(a=b.charAt(H),a>="0"&&a<="9");H++);if(b.charAt(H)=="."){for(f=++H;f<c&&(a=b.charAt(f),a>="0"&&a<="9");f++);f==H&&C();H=f}a=b.charAt(H);if(a=="e"||a=="E"){a=b.charAt(++H);(a=="+"||a=="-")&&H++;for(f=H;f<c&&(a=b.charAt(f),a>="0"&&a<="9");f++);f==H&&C();H=f}return+b.slice(j,H)}o&&C();if(b.slice(H,H+4)=="true"){H=H+4;return i}if(b.slice(H,H+5)==
+"false"){H=H+5;return false}if(b.slice(H,H+4)=="null"){H=H+4;return k}}C()}}return"$"},E=function(b){var c,a;b=="$"&&C();if(typeof b=="string"){if(b.charAt(0)=="@")return b.slice(1);if(b=="["){for(c=[];;a||(a=i)){b=D();if(b=="]")break;if(a)if(b==","){b=D();b=="]"&&C()}else C();b==","&&C();c.push(E(b))}return c}if(b=="{"){for(c={};;a||(a=i)){b=D();if(b=="}")break;if(a)if(b==","){b=D();b=="}"&&C()}else C();(b==","||typeof b!="string"||b.charAt(0)!="@"||D()!=":")&&C();c[b.slice(1)]=E(D())}return c}C()}return b},
+G=function(b,c,a){a=F(b,c,a);a===e?delete b[c]:b[c]=a},F=function(b,c,a){var d=b[c],j;if(typeof d=="object"&&d)if(l.call(d)=="[object Array]")for(j=d.length;j--;)G(d,j,a);else n(d,function(b){G(d,b,a)});return a.call(b,c,d)},q.parse=function(b,c){var a,d;H=0;I=b;a=E(D());D()!="$"&&C();H=I=k;return c&&l.call(c)=="[object Function]"?F((d={},d[""]=a,d),"",c):a})}p&&define(function(){return q});
+}());
+})();

+ 26 - 0
editor/js/libs/ternjs/signal.js

@@ -0,0 +1,26 @@
+(function(root, mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports);
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports"], mod);
+  mod((root.tern || (root.tern = {})).signal = {}); // Plain browser env
+})(this, function(exports) {
+  function on(type, f) {
+    var handlers = this._handlers || (this._handlers = Object.create(null));
+    (handlers[type] || (handlers[type] = [])).push(f);
+  }
+  function off(type, f) {
+    var arr = this._handlers && this._handlers[type];
+    if (arr) for (var i = 0; i < arr.length; ++i)
+      if (arr[i] == f) { arr.splice(i, 1); break; }
+  }
+  function signal(type, a1, a2, a3, a4) {
+    var arr = this._handlers && this._handlers[type];
+    if (arr) for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4);
+  }
+
+  exports.mixin = function(obj) {
+    obj.on = on; obj.off = off; obj.signal = signal;
+    return obj;
+  };
+});

+ 994 - 0
editor/js/libs/ternjs/tern.js

@@ -0,0 +1,994 @@
+// The Tern server object
+
+// A server is a stateful object that manages the analysis for a
+// project, and defines an interface for querying the code in the
+// project.
+
+(function(root, mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports, require("./infer"), require("./signal"),
+               require("acorn"), require("acorn/dist/walk"));
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports", "./infer", "./signal", "acorn/dist/acorn", "acorn/dist/walk"], mod);
+  mod(root.tern || (root.tern = {}), tern, tern.signal, acorn, acorn.walk); // Plain browser env
+})(this, function(exports, infer, signal, acorn, walk) {
+  "use strict";
+
+  var plugins = Object.create(null);
+  exports.registerPlugin = function(name, init) { plugins[name] = init; };
+
+  var defaultOptions = exports.defaultOptions = {
+    debug: false,
+    async: false,
+    getFile: function(_f, c) { if (this.async) c(null, null); },
+    defs: [],
+    plugins: {},
+    fetchTimeout: 1000,
+    dependencyBudget: 20000,
+    reuseInstances: true,
+    stripCRs: false
+  };
+
+  var queryTypes = {
+    completions: {
+      takesFile: true,
+      run: findCompletions
+    },
+    properties: {
+      run: findProperties
+    },
+    type: {
+      takesFile: true,
+      run: findTypeAt
+    },
+    documentation: {
+      takesFile: true,
+      run: findDocs
+    },
+    definition: {
+      takesFile: true,
+      run: findDef
+    },
+    refs: {
+      takesFile: true,
+      fullFile: true,
+      run: findRefs
+    },
+    rename: {
+      takesFile: true,
+      fullFile: true,
+      run: buildRename
+    },
+    files: {
+      run: listFiles
+    }
+  };
+
+  exports.defineQueryType = function(name, desc) { queryTypes[name] = desc; };
+
+  function File(name, parent) {
+    this.name = name;
+    this.parent = parent;
+    this.scope = this.text = this.ast = this.lineOffsets = null;
+  }
+  File.prototype.asLineChar = function(pos) { return asLineChar(this, pos); };
+
+  function updateText(file, text, srv) {
+    file.text = srv.options.stripCRs ? text.replace(/\r\n/g, "\n") : text;
+    infer.withContext(srv.cx, function() {
+      file.ast = infer.parse(file.text, srv.passes, {directSourceFile: file, allowReturnOutsideFunction: true});
+    });
+    file.lineOffsets = null;
+  }
+
+  var Server = exports.Server = function(options) {
+    this.cx = null;
+    this.options = options || {};
+    for (var o in defaultOptions) if (!options.hasOwnProperty(o))
+      options[o] = defaultOptions[o];
+
+    this.handlers = Object.create(null);
+    this.files = [];
+    this.fileMap = Object.create(null);
+    this.needsPurge = [];
+    this.budgets = Object.create(null);
+    this.uses = 0;
+    this.pending = 0;
+    this.asyncError = null;
+    this.passes = Object.create(null);
+
+    this.defs = options.defs.slice(0);
+    for (var plugin in options.plugins) if (options.plugins.hasOwnProperty(plugin) && plugin in plugins) {
+      var init = plugins[plugin](this, options.plugins[plugin]);
+      if (init && init.defs) {
+        if (init.loadFirst) this.defs.unshift(init.defs);
+        else this.defs.push(init.defs);
+      }
+      if (init && init.passes) for (var type in init.passes) if (init.passes.hasOwnProperty(type))
+        (this.passes[type] || (this.passes[type] = [])).push(init.passes[type]);
+    }
+
+    this.reset();
+  };
+  Server.prototype = signal.mixin({
+    addFile: function(name, /*optional*/ text, parent) {
+      // Don't crash when sloppy plugins pass non-existent parent ids
+      if (parent && !(parent in this.fileMap)) parent = null;
+      ensureFile(this, name, parent, text);
+    },
+    delFile: function(name) {
+      var file = this.findFile(name);
+      if (file) {
+        this.needsPurge.push(file.name);
+        this.files.splice(this.files.indexOf(file), 1);
+        delete this.fileMap[name];
+      }
+    },
+    reset: function() {
+      this.signal("reset");
+      this.cx = new infer.Context(this.defs, this);
+      this.uses = 0;
+      this.budgets = Object.create(null);
+      for (var i = 0; i < this.files.length; ++i) {
+        var file = this.files[i];
+        file.scope = null;
+      }
+    },
+
+    request: function(doc, c) {
+      var inv = invalidDoc(doc);
+      if (inv) return c(inv);
+
+      var self = this;
+      doRequest(this, doc, function(err, data) {
+        c(err, data);
+        if (self.uses > 40) {
+          self.reset();
+          analyzeAll(self, null, function(){});
+        }
+      });
+    },
+
+    findFile: function(name) {
+      return this.fileMap[name];
+    },
+
+    flush: function(c) {
+      var cx = this.cx;
+      analyzeAll(this, null, function(err) {
+        if (err) return c(err);
+        infer.withContext(cx, c);
+      });
+    },
+
+    startAsyncAction: function() {
+      ++this.pending;
+    },
+    finishAsyncAction: function(err) {
+      if (err) this.asyncError = err;
+      if (--this.pending === 0) this.signal("everythingFetched");
+    }
+  });
+
+  function doRequest(srv, doc, c) {
+    if (doc.query && !queryTypes.hasOwnProperty(doc.query.type))
+      return c("No query type '" + doc.query.type + "' defined");
+
+    var query = doc.query;
+    // Respond as soon as possible when this just uploads files
+    if (!query) c(null, {});
+
+    var files = doc.files || [];
+    if (files.length) ++srv.uses;
+    for (var i = 0; i < files.length; ++i) {
+      var file = files[i];
+      if (file.type == "delete")
+        srv.delFile(file.name);
+      else
+        ensureFile(srv, file.name, null, file.type == "full" ? file.text : null);
+    }
+
+    var timeBudget = typeof doc.timeout == "number" ? [doc.timeout] : null;
+    if (!query) {
+      analyzeAll(srv, timeBudget, function(){});
+      return;
+    }
+
+    var queryType = queryTypes[query.type];
+    if (queryType.takesFile) {
+      if (typeof query.file != "string") return c(".query.file must be a string");
+      if (!/^#/.test(query.file)) ensureFile(srv, query.file, null);
+    }
+
+    analyzeAll(srv, timeBudget, function(err) {
+      if (err) return c(err);
+      var file = queryType.takesFile && resolveFile(srv, files, query.file);
+      if (queryType.fullFile && file.type == "part")
+        return c("Can't run a " + query.type + " query on a file fragment");
+
+      function run() {
+        var result;
+        try {
+          result = queryType.run(srv, query, file);
+        } catch (e) {
+          if (srv.options.debug && e.name != "TernError") console.error(e.stack);
+          return c(e);
+        }
+        c(null, result);
+      }
+      infer.withContext(srv.cx, timeBudget ? function() { infer.withTimeout(timeBudget[0], run); } : run);
+    });
+  }
+
+  function analyzeFile(srv, file) {
+    infer.withContext(srv.cx, function() {
+      file.scope = srv.cx.topScope;
+      srv.signal("beforeLoad", file);
+      infer.analyze(file.ast, file.name, file.scope, srv.passes);
+      srv.signal("afterLoad", file);
+    });
+    return file;
+  }
+
+  function ensureFile(srv, name, parent, text) {
+    var known = srv.findFile(name);
+    if (known) {
+      if (text != null) {
+        if (known.scope) {
+          srv.needsPurge.push(name);
+          known.scope = null;
+        }
+        updateText(known, text, srv);
+      }
+      if (parentDepth(srv, known.parent) > parentDepth(srv, parent)) {
+        known.parent = parent;
+        if (known.excluded) known.excluded = null;
+      }
+      return;
+    }
+
+    var file = new File(name, parent);
+    srv.files.push(file);
+    srv.fileMap[name] = file;
+    if (text != null) {
+      updateText(file, text, srv);
+    } else if (srv.options.async) {
+      srv.startAsyncAction();
+      srv.options.getFile(name, function(err, text) {
+        updateText(file, text || "", srv);
+        srv.finishAsyncAction(err);
+      });
+    } else {
+      updateText(file, srv.options.getFile(name) || "", srv);
+    }
+  }
+
+  function fetchAll(srv, c) {
+    var done = true, returned = false;
+    srv.files.forEach(function(file) {
+      if (file.text != null) return;
+      if (srv.options.async) {
+        done = false;
+        srv.options.getFile(file.name, function(err, text) {
+          if (err && !returned) { returned = true; return c(err); }
+          updateText(file, text || "", srv);
+          fetchAll(srv, c);
+        });
+      } else {
+        try {
+          updateText(file, srv.options.getFile(file.name) || "", srv);
+        } catch (e) { return c(e); }
+      }
+    });
+    if (done) c();
+  }
+
+  function waitOnFetch(srv, timeBudget, c) {
+    var done = function() {
+      srv.off("everythingFetched", done);
+      clearTimeout(timeout);
+      analyzeAll(srv, timeBudget, c);
+    };
+    srv.on("everythingFetched", done);
+    var timeout = setTimeout(done, srv.options.fetchTimeout);
+  }
+
+  function analyzeAll(srv, timeBudget, c) {
+    if (srv.pending) return waitOnFetch(srv, timeBudget, c);
+
+    var e = srv.fetchError;
+    if (e) { srv.fetchError = null; return c(e); }
+
+    if (srv.needsPurge.length > 0) infer.withContext(srv.cx, function() {
+      infer.purge(srv.needsPurge);
+      srv.needsPurge.length = 0;
+    });
+
+    var done = true;
+    // The second inner loop might add new files. The outer loop keeps
+    // repeating both inner loops until all files have been looked at.
+    for (var i = 0; i < srv.files.length;) {
+      var toAnalyze = [];
+      for (; i < srv.files.length; ++i) {
+        var file = srv.files[i];
+        if (file.text == null) done = false;
+        else if (file.scope == null && !file.excluded) toAnalyze.push(file);
+      }
+      toAnalyze.sort(function(a, b) {
+        return parentDepth(srv, a.parent) - parentDepth(srv, b.parent);
+      });
+      for (var j = 0; j < toAnalyze.length; j++) {
+        var file = toAnalyze[j];
+        if (file.parent && !chargeOnBudget(srv, file)) {
+          file.excluded = true;
+        } else if (timeBudget) {
+          var startTime = +new Date;
+          infer.withTimeout(timeBudget[0], function() { analyzeFile(srv, file); });
+          timeBudget[0] -= +new Date - startTime;
+        } else {
+          analyzeFile(srv, file);
+        }
+      }
+    }
+    if (done) c();
+    else waitOnFetch(srv, timeBudget, c);
+  }
+
+  function firstLine(str) {
+    var end = str.indexOf("\n");
+    if (end < 0) return str;
+    return str.slice(0, end);
+  }
+
+  function findMatchingPosition(line, file, near) {
+    var pos = Math.max(0, near - 500), closest = null;
+    if (!/^\s*$/.test(line)) for (;;) {
+      var found = file.indexOf(line, pos);
+      if (found < 0 || found > near + 500) break;
+      if (closest == null || Math.abs(closest - near) > Math.abs(found - near))
+        closest = found;
+      pos = found + line.length;
+    }
+    return closest;
+  }
+
+  function scopeDepth(s) {
+    for (var i = 0; s; ++i, s = s.prev) {}
+    return i;
+  }
+
+  function ternError(msg) {
+    var err = new Error(msg);
+    err.name = "TernError";
+    return err;
+  }
+
+  function resolveFile(srv, localFiles, name) {
+    var isRef = name.match(/^#(\d+)$/);
+    if (!isRef) return srv.findFile(name);
+
+    var file = localFiles[isRef[1]];
+    if (!file || file.type == "delete") throw ternError("Reference to unknown file " + name);
+    if (file.type == "full") return srv.findFile(file.name);
+
+    // This is a partial file
+
+    var realFile = file.backing = srv.findFile(file.name);
+    var offset = file.offset;
+    if (file.offsetLines) offset = {line: file.offsetLines, ch: 0};
+    file.offset = offset = resolvePos(realFile, file.offsetLines == null ? file.offset : {line: file.offsetLines, ch: 0}, true);
+    var line = firstLine(file.text);
+    var foundPos = findMatchingPosition(line, realFile.text, offset);
+    var pos = foundPos == null ? Math.max(0, realFile.text.lastIndexOf("\n", offset)) : foundPos;
+    var inObject, atFunction;
+
+    infer.withContext(srv.cx, function() {
+      infer.purge(file.name, pos, pos + file.text.length);
+
+      var text = file.text, m;
+      if (m = text.match(/(?:"([^"]*)"|([\w$]+))\s*:\s*function\b/)) {
+        var objNode = walk.findNodeAround(file.backing.ast, pos, "ObjectExpression");
+        if (objNode && objNode.node.objType)
+          inObject = {type: objNode.node.objType, prop: m[2] || m[1]};
+      }
+      if (foundPos && (m = line.match(/^(.*?)\bfunction\b/))) {
+        var cut = m[1].length, white = "";
+        for (var i = 0; i < cut; ++i) white += " ";
+        text = white + text.slice(cut);
+        atFunction = true;
+      }
+
+      var scopeStart = infer.scopeAt(realFile.ast, pos, realFile.scope);
+      var scopeEnd = infer.scopeAt(realFile.ast, pos + text.length, realFile.scope);
+      var scope = file.scope = scopeDepth(scopeStart) < scopeDepth(scopeEnd) ? scopeEnd : scopeStart;
+      file.ast = infer.parse(text, srv.passes, {directSourceFile: file, allowReturnOutsideFunction: true});
+      infer.analyze(file.ast, file.name, scope, srv.passes);
+
+      // This is a kludge to tie together the function types (if any)
+      // outside and inside of the fragment, so that arguments and
+      // return values have some information known about them.
+      tieTogether: if (inObject || atFunction) {
+        var newInner = infer.scopeAt(file.ast, line.length, scopeStart);
+        if (!newInner.fnType) break tieTogether;
+        if (inObject) {
+          var prop = inObject.type.getProp(inObject.prop);
+          prop.addType(newInner.fnType);
+        } else if (atFunction) {
+          var inner = infer.scopeAt(realFile.ast, pos + line.length, realFile.scope);
+          if (inner == scopeStart || !inner.fnType) break tieTogether;
+          var fOld = inner.fnType, fNew = newInner.fnType;
+          if (!fNew || (fNew.name != fOld.name && fOld.name)) break tieTogether;
+          for (var i = 0, e = Math.min(fOld.args.length, fNew.args.length); i < e; ++i)
+            fOld.args[i].propagate(fNew.args[i]);
+          fOld.self.propagate(fNew.self);
+          fNew.retval.propagate(fOld.retval);
+        }
+      }
+    });
+    return file;
+  }
+
+  // Budget management
+
+  function astSize(node) {
+    var size = 0;
+    walk.simple(node, {Expression: function() { ++size; }});
+    return size;
+  }
+
+  function parentDepth(srv, parent) {
+    var depth = 0;
+    while (parent) {
+      parent = srv.findFile(parent).parent;
+      ++depth;
+    }
+    return depth;
+  }
+
+  function budgetName(srv, file) {
+    for (;;) {
+      var parent = srv.findFile(file.parent);
+      if (!parent.parent) break;
+      file = parent;
+    }
+    return file.name;
+  }
+
+  function chargeOnBudget(srv, file) {
+    var bName = budgetName(srv, file);
+    var size = astSize(file.ast);
+    var known = srv.budgets[bName];
+    if (known == null)
+      known = srv.budgets[bName] = srv.options.dependencyBudget;
+    if (known < size) return false;
+    srv.budgets[bName] = known - size;
+    return true;
+  }
+
+  // Query helpers
+
+  function isPosition(val) {
+    return typeof val == "number" || typeof val == "object" &&
+      typeof val.line == "number" && typeof val.ch == "number";
+  }
+
+  // Baseline query document validation
+  function invalidDoc(doc) {
+    if (doc.query) {
+      if (typeof doc.query.type != "string") return ".query.type must be a string";
+      if (doc.query.start && !isPosition(doc.query.start)) return ".query.start must be a position";
+      if (doc.query.end && !isPosition(doc.query.end)) return ".query.end must be a position";
+    }
+    if (doc.files) {
+      if (!Array.isArray(doc.files)) return "Files property must be an array";
+      for (var i = 0; i < doc.files.length; ++i) {
+        var file = doc.files[i];
+        if (typeof file != "object") return ".files[n] must be objects";
+        else if (typeof file.name != "string") return ".files[n].name must be a string";
+        else if (file.type == "delete") continue;
+        else if (typeof file.text != "string") return ".files[n].text must be a string";
+        else if (file.type == "part") {
+          if (!isPosition(file.offset) && typeof file.offsetLines != "number")
+            return ".files[n].offset must be a position";
+        } else if (file.type != "full") return ".files[n].type must be \"full\" or \"part\"";
+      }
+    }
+  }
+
+  var offsetSkipLines = 25;
+
+  function findLineStart(file, line) {
+    var text = file.text, offsets = file.lineOffsets || (file.lineOffsets = [0]);
+    var pos = 0, curLine = 0;
+    var storePos = Math.min(Math.floor(line / offsetSkipLines), offsets.length - 1);
+    var pos = offsets[storePos], curLine = storePos * offsetSkipLines;
+
+    while (curLine < line) {
+      ++curLine;
+      pos = text.indexOf("\n", pos) + 1;
+      if (pos === 0) return null;
+      if (curLine % offsetSkipLines === 0) offsets.push(pos);
+    }
+    return pos;
+  }
+
+  var resolvePos = exports.resolvePos = function(file, pos, tolerant) {
+    if (typeof pos != "number") {
+      var lineStart = findLineStart(file, pos.line);
+      if (lineStart == null) {
+        if (tolerant) pos = file.text.length;
+        else throw ternError("File doesn't contain a line " + pos.line);
+      } else {
+        pos = lineStart + pos.ch;
+      }
+    }
+    if (pos > file.text.length) {
+      if (tolerant) pos = file.text.length;
+      else throw ternError("Position " + pos + " is outside of file.");
+    }
+    return pos;
+  };
+
+  function asLineChar(file, pos) {
+    if (!file) return {line: 0, ch: 0};
+    var offsets = file.lineOffsets || (file.lineOffsets = [0]);
+    var text = file.text, line, lineStart;
+    for (var i = offsets.length - 1; i >= 0; --i) if (offsets[i] <= pos) {
+      line = i * offsetSkipLines;
+      lineStart = offsets[i];
+    }
+    for (;;) {
+      var eol = text.indexOf("\n", lineStart);
+      if (eol >= pos || eol < 0) break;
+      lineStart = eol + 1;
+      ++line;
+    }
+    return {line: line, ch: pos - lineStart};
+  }
+
+  var outputPos = exports.outputPos = function(query, file, pos) {
+    if (query.lineCharPositions) {
+      var out = asLineChar(file, pos);
+      if (file.type == "part")
+        out.line += file.offsetLines != null ? file.offsetLines : asLineChar(file.backing, file.offset).line;
+      return out;
+    } else {
+      return pos + (file.type == "part" ? file.offset : 0);
+    }
+  };
+
+  // Delete empty fields from result objects
+  function clean(obj) {
+    for (var prop in obj) if (obj[prop] == null) delete obj[prop];
+    return obj;
+  }
+  function maybeSet(obj, prop, val) {
+    if (val != null) obj[prop] = val;
+  }
+
+  // Built-in query types
+
+  function compareCompletions(a, b) {
+    if (typeof a != "string") { a = a.name; b = b.name; }
+    var aUp = /^[A-Z]/.test(a), bUp = /^[A-Z]/.test(b);
+    if (aUp == bUp) return a < b ? -1 : a == b ? 0 : 1;
+    else return aUp ? 1 : -1;
+  }
+
+  function isStringAround(node, start, end) {
+    return node.type == "Literal" && typeof node.value == "string" &&
+      node.start == start - 1 && node.end <= end + 1;
+  }
+
+  function pointInProp(objNode, point) {
+    for (var i = 0; i < objNode.properties.length; i++) {
+      var curProp = objNode.properties[i];
+      if (curProp.key.start <= point && curProp.key.end >= point)
+        return curProp;
+    }
+  }
+
+  var jsKeywords = ("break do instanceof typeof case else new var " +
+    "catch finally return void continue for switch while debugger " +
+    "function this with default if throw delete in try").split(" ");
+
+  function findCompletions(srv, query, file) {
+    if (query.end == null) throw ternError("missing .query.end field");
+    if (srv.passes.completion) for (var i = 0; i < srv.passes.completion.length; i++) {
+      var result = srv.passes.completion[i](file, query);
+      if (result) return result;
+    }
+
+    var wordStart = resolvePos(file, query.end), wordEnd = wordStart, text = file.text;
+    while (wordStart && acorn.isIdentifierChar(text.charCodeAt(wordStart - 1))) --wordStart;
+    if (query.expandWordForward !== false)
+      while (wordEnd < text.length && acorn.isIdentifierChar(text.charCodeAt(wordEnd))) ++wordEnd;
+    var word = text.slice(wordStart, wordEnd), completions = [], ignoreObj;
+    if (query.caseInsensitive) word = word.toLowerCase();
+    var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins;
+
+    function gather(prop, obj, depth, addInfo) {
+      // 'hasOwnProperty' and such are usually just noise, leave them
+      // out when no prefix is provided.
+      if ((objLit || query.omitObjectPrototype !== false) && obj == srv.cx.protos.Object && !word) return;
+      if (query.filter !== false && word &&
+          (query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) !== 0) return;
+      if (ignoreObj && ignoreObj.props[prop]) return;
+      for (var i = 0; i < completions.length; ++i) {
+        var c = completions[i];
+        if ((wrapAsObjs ? c.name : c) == prop) return;
+      }
+      var rec = wrapAsObjs ? {name: prop} : prop;
+      completions.push(rec);
+
+      if (obj && (query.types || query.docs || query.urls || query.origins)) {
+        var val = obj.props[prop];
+        infer.resetGuessing();
+        var type = val.getType();
+        rec.guess = infer.didGuess();
+        if (query.types)
+          rec.type = infer.toString(val);
+        if (query.docs)
+          maybeSet(rec, "doc", val.doc || type && type.doc);
+        if (query.urls)
+          maybeSet(rec, "url", val.url || type && type.url);
+        if (query.origins)
+          maybeSet(rec, "origin", val.origin || type && type.origin);
+      }
+      if (query.depths) rec.depth = depth;
+      if (wrapAsObjs && addInfo) addInfo(rec);
+    }
+
+    var hookname, prop, objType, isKey;
+
+    var exprAt = infer.findExpressionAround(file.ast, null, wordStart, file.scope);
+    var memberExpr, objLit;
+    // Decide whether this is an object property, either in a member
+    // expression or an object literal.
+    if (exprAt) {
+      if (exprAt.node.type == "MemberExpression" && exprAt.node.object.end < wordStart) {
+        memberExpr = exprAt;
+      } else if (isStringAround(exprAt.node, wordStart, wordEnd)) {
+        var parent = infer.parentNode(exprAt.node, file.ast);
+        if (parent.type == "MemberExpression" && parent.property == exprAt.node)
+          memberExpr = {node: parent, state: exprAt.state};
+      } else if (exprAt.node.type == "ObjectExpression") {
+        var objProp = pointInProp(exprAt.node, wordEnd);
+        if (objProp) {
+          objLit = exprAt;
+          prop = isKey = objProp.key.name;
+        } else if (!word && !/:\s*$/.test(file.text.slice(0, wordStart))) {
+          objLit = exprAt;
+          prop = isKey = true;
+        }
+      }
+    }
+
+    if (objLit) {
+      // Since we can't use the type of the literal itself to complete
+      // its properties (it doesn't contain the information we need),
+      // we have to try asking the surrounding expression for type info.
+      objType = infer.typeFromContext(file.ast, objLit);
+      ignoreObj = objLit.node.objType;
+    } else if (memberExpr) {
+      prop = memberExpr.node.property;
+      prop = prop.type == "Literal" ? prop.value.slice(1) : prop.name;
+      memberExpr.node = memberExpr.node.object;
+      objType = infer.expressionType(memberExpr);
+    } else if (text.charAt(wordStart - 1) == ".") {
+      var pathStart = wordStart - 1;
+      while (pathStart && (text.charAt(pathStart - 1) == "." || acorn.isIdentifierChar(text.charCodeAt(pathStart - 1)))) pathStart--;
+      var path = text.slice(pathStart, wordStart - 1);
+      if (path) {
+        objType = infer.def.parsePath(path, file.scope).getObjType();
+        prop = word;
+      }
+    }
+
+    if (prop != null) {
+      srv.cx.completingProperty = prop;
+
+      if (objType) infer.forAllPropertiesOf(objType, gather);
+
+      if (!completions.length && query.guess !== false && objType && objType.guessProperties)
+        objType.guessProperties(function(p, o, d) {if (p != prop && p != "✖") gather(p, o, d);});
+      if (!completions.length && word.length >= 2 && query.guess !== false)
+        for (var prop in srv.cx.props) gather(prop, srv.cx.props[prop][0], 0);
+      hookname = "memberCompletion";
+    } else {
+      infer.forAllLocalsAt(file.ast, wordStart, file.scope, gather);
+      if (query.includeKeywords) jsKeywords.forEach(function(kw) {
+        gather(kw, null, 0, function(rec) { rec.isKeyword = true; });
+      });
+      hookname = "variableCompletion";
+    }
+    if (srv.passes[hookname])
+      srv.passes[hookname].forEach(function(hook) {hook(file, wordStart, wordEnd, gather);});
+
+    if (query.sort !== false) completions.sort(compareCompletions);
+    srv.cx.completingProperty = null;
+
+    return {start: outputPos(query, file, wordStart),
+            end: outputPos(query, file, wordEnd),
+            isProperty: !!prop,
+            isObjectKey: !!isKey,
+            completions: completions};
+  }
+
+  function findProperties(srv, query) {
+    var prefix = query.prefix, found = [];
+    for (var prop in srv.cx.props)
+      if (prop != "<i>" && (!prefix || prop.indexOf(prefix) === 0)) found.push(prop);
+    if (query.sort !== false) found.sort(compareCompletions);
+    return {completions: found};
+  }
+
+  var findExpr = exports.findQueryExpr = function(file, query, wide) {
+    if (query.end == null) throw ternError("missing .query.end field");
+
+    if (query.variable) {
+      var scope = infer.scopeAt(file.ast, resolvePos(file, query.end), file.scope);
+      return {node: {type: "Identifier", name: query.variable, start: query.end, end: query.end + 1},
+              state: scope};
+    } else {
+      var start = query.start && resolvePos(file, query.start), end = resolvePos(file, query.end);
+      var expr = infer.findExpressionAt(file.ast, start, end, file.scope);
+      if (expr) return expr;
+      expr = infer.findExpressionAround(file.ast, start, end, file.scope);
+      if (expr && (expr.node.type == "ObjectExpression" || wide ||
+                   (start == null ? end : start) - expr.node.start < 20 || expr.node.end - end < 20))
+        return expr;
+      return null;
+    }
+  };
+
+  function findExprOrThrow(file, query, wide) {
+    var expr = findExpr(file, query, wide);
+    if (expr) return expr;
+    throw ternError("No expression at the given position.");
+  }
+
+  function ensureObj(tp) {
+    if (!tp || !(tp = tp.getType()) || !(tp instanceof infer.Obj)) return null;
+    return tp;
+  }
+
+  function findExprType(srv, query, file, expr) {
+    var type;
+    if (expr) {
+      infer.resetGuessing();
+      type = infer.expressionType(expr);
+    }
+    if (srv.passes["typeAt"]) {
+      var pos = resolvePos(file, query.end);
+      srv.passes["typeAt"].forEach(function(hook) {
+        type = hook(file, pos, expr, type);
+      });
+    }
+    if (!type) throw ternError("No type found at the given position.");
+
+    var objProp;
+    if (expr.node.type == "ObjectExpression" && query.end != null &&
+        (objProp = pointInProp(expr.node, resolvePos(file, query.end)))) {
+      var name = objProp.key.name;
+      var fromCx = ensureObj(infer.typeFromContext(file.ast, expr));
+      if (fromCx && fromCx.hasProp(name)) {
+        type = fromCx.hasProp(name);
+      } else {
+        var fromLocal = ensureObj(type);
+        if (fromLocal && fromLocal.hasProp(name))
+          type = fromLocal.hasProp(name);
+      }
+    }
+    return type;
+  };
+
+  function findTypeAt(srv, query, file) {
+    var expr = findExpr(file, query), exprName;
+    var type = findExprType(srv, query, file, expr), exprType = type;
+    if (query.preferFunction)
+      type = type.getFunctionType() || type.getType();
+    else
+      type = type.getType();
+
+    if (expr) {
+      if (expr.node.type == "Identifier")
+        exprName = expr.node.name;
+      else if (expr.node.type == "MemberExpression" && !expr.node.computed)
+        exprName = expr.node.property.name;
+    }
+
+    if (query.depth != null && typeof query.depth != "number")
+      throw ternError(".query.depth must be a number");
+
+    var result = {guess: infer.didGuess(),
+                  type: infer.toString(exprType, query.depth),
+                  name: type && type.name,
+                  exprName: exprName};
+    if (type) storeTypeDocs(type, result);
+    if (!result.doc && exprType.doc) result.doc = exprType.doc;
+
+    return clean(result);
+  }
+
+  function findDocs(srv, query, file) {
+    var expr = findExpr(file, query);
+    var type = findExprType(srv, query, file, expr);
+    var result = {url: type.url, doc: type.doc, type: infer.toString(type)};
+    var inner = type.getType();
+    if (inner) storeTypeDocs(inner, result);
+    return clean(result);
+  }
+
+  function storeTypeDocs(type, out) {
+    if (!out.url) out.url = type.url;
+    if (!out.doc) out.doc = type.doc;
+    if (!out.origin) out.origin = type.origin;
+    var ctor, boring = infer.cx().protos;
+    if (!out.url && !out.doc && type.proto && (ctor = type.proto.hasCtor) &&
+        type.proto != boring.Object && type.proto != boring.Function && type.proto != boring.Array) {
+      out.url = ctor.url;
+      out.doc = ctor.doc;
+    }
+  }
+
+  var getSpan = exports.getSpan = function(obj) {
+    if (!obj.origin) return;
+    if (obj.originNode) {
+      var node = obj.originNode;
+      if (/^Function/.test(node.type) && node.id) node = node.id;
+      return {origin: obj.origin, node: node};
+    }
+    if (obj.span) return {origin: obj.origin, span: obj.span};
+  };
+
+  var storeSpan = exports.storeSpan = function(srv, query, span, target) {
+    target.origin = span.origin;
+    if (span.span) {
+      var m = /^(\d+)\[(\d+):(\d+)\]-(\d+)\[(\d+):(\d+)\]$/.exec(span.span);
+      target.start = query.lineCharPositions ? {line: Number(m[2]), ch: Number(m[3])} : Number(m[1]);
+      target.end = query.lineCharPositions ? {line: Number(m[5]), ch: Number(m[6])} : Number(m[4]);
+    } else {
+      var file = srv.findFile(span.origin);
+      target.start = outputPos(query, file, span.node.start);
+      target.end = outputPos(query, file, span.node.end);
+    }
+  };
+
+  function findDef(srv, query, file) {
+    var expr = findExpr(file, query);
+    var type = findExprType(srv, query, file, expr);
+    if (infer.didGuess()) return {};
+
+    var span = getSpan(type);
+    var result = {url: type.url, doc: type.doc, origin: type.origin};
+
+    if (type.types) for (var i = type.types.length - 1; i >= 0; --i) {
+      var tp = type.types[i];
+      storeTypeDocs(tp, result);
+      if (!span) span = getSpan(tp);
+    }
+
+    if (span && span.node) { // refers to a loaded file
+      var spanFile = span.node.sourceFile || srv.findFile(span.origin);
+      var start = outputPos(query, spanFile, span.node.start), end = outputPos(query, spanFile, span.node.end);
+      result.start = start; result.end = end;
+      result.file = span.origin;
+      var cxStart = Math.max(0, span.node.start - 50);
+      result.contextOffset = span.node.start - cxStart;
+      result.context = spanFile.text.slice(cxStart, cxStart + 50);
+    } else if (span) { // external
+      result.file = span.origin;
+      storeSpan(srv, query, span, result);
+    }
+    return clean(result);
+  }
+
+  function findRefsToVariable(srv, query, file, expr, checkShadowing) {
+    var name = expr.node.name;
+
+    for (var scope = expr.state; scope && !(name in scope.props); scope = scope.prev) {}
+    if (!scope) throw ternError("Could not find a definition for " + name + " " + !!srv.cx.topScope.props.x);
+
+    var type, refs = [];
+    function storeRef(file) {
+      return function(node, scopeHere) {
+        if (checkShadowing) for (var s = scopeHere; s != scope; s = s.prev) {
+          var exists = s.hasProp(checkShadowing);
+          if (exists)
+            throw ternError("Renaming `" + name + "` to `" + checkShadowing + "` would make a variable at line " +
+                            (asLineChar(file, node.start).line + 1) + " point to the definition at line " +
+                            (asLineChar(file, exists.name.start).line + 1));
+        }
+        refs.push({file: file.name,
+                   start: outputPos(query, file, node.start),
+                   end: outputPos(query, file, node.end)});
+      };
+    }
+
+    if (scope.originNode) {
+      type = "local";
+      if (checkShadowing) {
+        for (var prev = scope.prev; prev; prev = prev.prev)
+          if (checkShadowing in prev.props) break;
+        if (prev) infer.findRefs(scope.originNode, scope, checkShadowing, prev, function(node) {
+          throw ternError("Renaming `" + name + "` to `" + checkShadowing + "` would shadow the definition used at line " +
+                          (asLineChar(file, node.start).line + 1));
+        });
+      }
+      infer.findRefs(scope.originNode, scope, name, scope, storeRef(file));
+    } else {
+      type = "global";
+      for (var i = 0; i < srv.files.length; ++i) {
+        var cur = srv.files[i];
+        infer.findRefs(cur.ast, cur.scope, name, scope, storeRef(cur));
+      }
+    }
+
+    return {refs: refs, type: type, name: name};
+  }
+
+  function findRefsToProperty(srv, query, expr, prop) {
+    var objType = infer.expressionType(expr).getObjType();
+    if (!objType) throw ternError("Couldn't determine type of base object.");
+
+    var refs = [];
+    function storeRef(file) {
+      return function(node) {
+        refs.push({file: file.name,
+                   start: outputPos(query, file, node.start),
+                   end: outputPos(query, file, node.end)});
+      };
+    }
+    for (var i = 0; i < srv.files.length; ++i) {
+      var cur = srv.files[i];
+      infer.findPropRefs(cur.ast, cur.scope, objType, prop.name, storeRef(cur));
+    }
+
+    return {refs: refs, name: prop.name};
+  }
+
+  function findRefs(srv, query, file) {
+    var expr = findExprOrThrow(file, query, true);
+    if (expr && expr.node.type == "Identifier") {
+      return findRefsToVariable(srv, query, file, expr);
+    } else if (expr && expr.node.type == "MemberExpression" && !expr.node.computed) {
+      var p = expr.node.property;
+      expr.node = expr.node.object;
+      return findRefsToProperty(srv, query, expr, p);
+    } else if (expr && expr.node.type == "ObjectExpression") {
+      var pos = resolvePos(file, query.end);
+      for (var i = 0; i < expr.node.properties.length; ++i) {
+        var k = expr.node.properties[i].key;
+        if (k.start <= pos && k.end >= pos)
+          return findRefsToProperty(srv, query, expr, k);
+      }
+    }
+    throw ternError("Not at a variable or property name.");
+  }
+
+  function buildRename(srv, query, file) {
+    if (typeof query.newName != "string") throw ternError(".query.newName should be a string");
+    var expr = findExprOrThrow(file, query);
+    if (!expr || expr.node.type != "Identifier") throw ternError("Not at a variable.");
+
+    var data = findRefsToVariable(srv, query, file, expr, query.newName), refs = data.refs;
+    delete data.refs;
+    data.files = srv.files.map(function(f){return f.name;});
+
+    var changes = data.changes = [];
+    for (var i = 0; i < refs.length; ++i) {
+      var use = refs[i];
+      use.text = query.newName;
+      changes.push(use);
+    }
+
+    return data;
+  }
+
+  function listFiles(srv) {
+    return {files: srv.files.map(function(f){return f.name;})};
+  }
+
+  exports.version = "0.11.1";
+});

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott