github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/app/public/codemirror/addon/tern/tern.js (about)

     1  // CodeMirror, copyright (c) by Marijn Haverbeke and others
     2  // Distributed under an MIT license: http://codemirror.net/LICENSE
     3  
     4  // Glue code between CodeMirror and Tern.
     5  //
     6  // Create a CodeMirror.TernServer to wrap an actual Tern server,
     7  // register open documents (CodeMirror.Doc instances) with it, and
     8  // call its methods to activate the assisting functions that Tern
     9  // provides.
    10  //
    11  // Options supported (all optional):
    12  // * defs: An array of JSON definition data structures.
    13  // * plugins: An object mapping plugin names to configuration
    14  //   options.
    15  // * getFile: A function(name, c) that can be used to access files in
    16  //   the project that haven't been loaded yet. Simply do c(null) to
    17  //   indicate that a file is not available.
    18  // * fileFilter: A function(value, docName, doc) that will be applied
    19  //   to documents before passing them on to Tern.
    20  // * switchToDoc: A function(name, doc) that should, when providing a
    21  //   multi-file view, switch the view or focus to the named file.
    22  // * showError: A function(editor, message) that can be used to
    23  //   override the way errors are displayed.
    24  // * completionTip: Customize the content in tooltips for completions.
    25  //   Is passed a single argument—the completion's data as returned by
    26  //   Tern—and may return a string, DOM node, or null to indicate that
    27  //   no tip should be shown. By default the docstring is shown.
    28  // * typeTip: Like completionTip, but for the tooltips shown for type
    29  //   queries.
    30  // * responseFilter: A function(doc, query, request, error, data) that
    31  //   will be applied to the Tern responses before treating them
    32  //
    33  //
    34  // It is possible to run the Tern server in a web worker by specifying
    35  // these additional options:
    36  // * useWorker: Set to true to enable web worker mode. You'll probably
    37  //   want to feature detect the actual value you use here, for example
    38  //   !!window.Worker.
    39  // * workerScript: The main script of the worker. Point this to
    40  //   wherever you are hosting worker.js from this directory.
    41  // * workerDeps: An array of paths pointing (relative to workerScript)
    42  //   to the Acorn and Tern libraries and any Tern plugins you want to
    43  //   load. Or, if you minified those into a single script and included
    44  //   them in the workerScript, simply leave this undefined.
    45  
    46  (function(mod) {
    47    if (typeof exports == "object" && typeof module == "object") // CommonJS
    48      mod(require("../../lib/codemirror"));
    49    else if (typeof define == "function" && define.amd) // AMD
    50      define(["../../lib/codemirror"], mod);
    51    else // Plain browser env
    52      mod(CodeMirror);
    53  })(function(CodeMirror) {
    54    "use strict";
    55    // declare global: tern
    56  
    57    CodeMirror.TernServer = function(options) {
    58      var self = this;
    59      this.options = options || {};
    60      var plugins = this.options.plugins || (this.options.plugins = {});
    61      if (!plugins.doc_comment) plugins.doc_comment = true;
    62      this.docs = Object.create(null);
    63      if (this.options.useWorker) {
    64        this.server = new WorkerServer(this);
    65      } else {
    66        this.server = new tern.Server({
    67          getFile: function(name, c) { return getFile(self, name, c); },
    68          async: true,
    69          defs: this.options.defs || [],
    70          plugins: plugins
    71        });
    72      }
    73      this.trackChange = function(doc, change) { trackChange(self, doc, change); };
    74  
    75      this.cachedArgHints = null;
    76      this.activeArgHints = null;
    77      this.jumpStack = [];
    78  
    79      this.getHint = function(cm, c) { return hint(self, cm, c); };
    80      this.getHint.async = true;
    81    };
    82  
    83    CodeMirror.TernServer.prototype = {
    84      addDoc: function(name, doc) {
    85        var data = {doc: doc, name: name, changed: null};
    86        this.server.addFile(name, docValue(this, data));
    87        CodeMirror.on(doc, "change", this.trackChange);
    88        return this.docs[name] = data;
    89      },
    90  
    91      delDoc: function(id) {
    92        var found = resolveDoc(this, id);
    93        if (!found) return;
    94        CodeMirror.off(found.doc, "change", this.trackChange);
    95        delete this.docs[found.name];
    96        this.server.delFile(found.name);
    97      },
    98  
    99      hideDoc: function(id) {
   100        closeArgHints(this);
   101        var found = resolveDoc(this, id);
   102        if (found && found.changed) sendDoc(this, found);
   103      },
   104  
   105      complete: function(cm) {
   106        cm.showHint({hint: this.getHint});
   107      },
   108  
   109      showType: function(cm, pos, c) { showContextInfo(this, cm, pos, "type", c); },
   110  
   111      showDocs: function(cm, pos, c) { showContextInfo(this, cm, pos, "documentation", c); },
   112  
   113      updateArgHints: function(cm) { updateArgHints(this, cm); },
   114  
   115      jumpToDef: function(cm) { jumpToDef(this, cm); },
   116  
   117      jumpBack: function(cm) { jumpBack(this, cm); },
   118  
   119      rename: function(cm) { rename(this, cm); },
   120  
   121      selectName: function(cm) { selectName(this, cm); },
   122  
   123      request: function (cm, query, c, pos) {
   124        var self = this;
   125        var doc = findDoc(this, cm.getDoc());
   126        var request = buildRequest(this, doc, query, pos);
   127        var extraOptions = request.query && this.options.queryOptions && this.options.queryOptions[request.query.type]
   128        if (extraOptions) for (var prop in extraOptions) request.query[prop] = extraOptions[prop];
   129  
   130        this.server.request(request, function (error, data) {
   131          if (!error && self.options.responseFilter)
   132            data = self.options.responseFilter(doc, query, request, error, data);
   133          c(error, data);
   134        });
   135      },
   136  
   137      destroy: function () {
   138        closeArgHints(this)
   139        if (this.worker) {
   140          this.worker.terminate();
   141          this.worker = null;
   142        }
   143      }
   144    };
   145  
   146    var Pos = CodeMirror.Pos;
   147    var cls = "CodeMirror-Tern-";
   148    var bigDoc = 250;
   149  
   150    function getFile(ts, name, c) {
   151      var buf = ts.docs[name];
   152      if (buf)
   153        c(docValue(ts, buf));
   154      else if (ts.options.getFile)
   155        ts.options.getFile(name, c);
   156      else
   157        c(null);
   158    }
   159  
   160    function findDoc(ts, doc, name) {
   161      for (var n in ts.docs) {
   162        var cur = ts.docs[n];
   163        if (cur.doc == doc) return cur;
   164      }
   165      if (!name) for (var i = 0;; ++i) {
   166        n = "[doc" + (i || "") + "]";
   167        if (!ts.docs[n]) { name = n; break; }
   168      }
   169      return ts.addDoc(name, doc);
   170    }
   171  
   172    function resolveDoc(ts, id) {
   173      if (typeof id == "string") return ts.docs[id];
   174      if (id instanceof CodeMirror) id = id.getDoc();
   175      if (id instanceof CodeMirror.Doc) return findDoc(ts, id);
   176    }
   177  
   178    function trackChange(ts, doc, change) {
   179      var data = findDoc(ts, doc);
   180  
   181      var argHints = ts.cachedArgHints;
   182      if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
   183        ts.cachedArgHints = null;
   184  
   185      var changed = data.changed;
   186      if (changed == null)
   187        data.changed = changed = {from: change.from.line, to: change.from.line};
   188      var end = change.from.line + (change.text.length - 1);
   189      if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end);
   190      if (end >= changed.to) changed.to = end + 1;
   191      if (changed.from > change.from.line) changed.from = change.from.line;
   192  
   193      if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() {
   194        if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data);
   195      }, 200);
   196    }
   197  
   198    function sendDoc(ts, doc) {
   199      ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) {
   200        if (error) window.console.error(error);
   201        else doc.changed = null;
   202      });
   203    }
   204  
   205    // Completion
   206  
   207    function hint(ts, cm, c) {
   208      ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) {
   209        if (error) return showError(ts, cm, error);
   210        var completions = [], after = "";
   211        var from = data.start, to = data.end;
   212        if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" &&
   213            cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]")
   214          after = "\"]";
   215  
   216        for (var i = 0; i < data.completions.length; ++i) {
   217          var completion = data.completions[i], className = typeToIcon(completion.type);
   218          if (data.guess) className += " " + cls + "guess";
   219          completions.push({text: completion.name + after,
   220                            displayText: completion.displayName || completion.name,
   221                            className: className,
   222                            data: completion});
   223        }
   224  
   225        var obj = {from: from, to: to, list: completions};
   226        var tooltip = null;
   227        CodeMirror.on(obj, "close", function() { remove(tooltip); });
   228        CodeMirror.on(obj, "update", function() { remove(tooltip); });
   229        CodeMirror.on(obj, "select", function(cur, node) {
   230          remove(tooltip);
   231          var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
   232          if (content) {
   233            tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
   234                                  node.getBoundingClientRect().top + window.pageYOffset, content);
   235            tooltip.className += " " + cls + "hint-doc";
   236          }
   237        });
   238        c(obj);
   239      });
   240    }
   241  
   242    function typeToIcon(type) {
   243      var suffix;
   244      if (type == "?") suffix = "unknown";
   245      else if (type == "number" || type == "string" || type == "bool") suffix = type;
   246      else if (/^fn\(/.test(type)) suffix = "fn";
   247      else if (/^\[/.test(type)) suffix = "array";
   248      else suffix = "object";
   249      return cls + "completion " + cls + "completion-" + suffix;
   250    }
   251  
   252    // Type queries
   253  
   254    function showContextInfo(ts, cm, pos, queryName, c) {
   255      ts.request(cm, queryName, function(error, data) {
   256        if (error) return showError(ts, cm, error);
   257        if (ts.options.typeTip) {
   258          var tip = ts.options.typeTip(data);
   259        } else {
   260          var tip = elt("span", null, elt("strong", null, data.type || "not found"));
   261          if (data.doc)
   262            tip.appendChild(document.createTextNode(" — " + data.doc));
   263          if (data.url) {
   264            tip.appendChild(document.createTextNode(" "));
   265            var child = tip.appendChild(elt("a", null, "[docs]"));
   266            child.href = data.url;
   267            child.target = "_blank";
   268          }
   269        }
   270        tempTooltip(cm, tip, ts);
   271        if (c) c();
   272      }, pos);
   273    }
   274  
   275    // Maintaining argument hints
   276  
   277    function updateArgHints(ts, cm) {
   278      closeArgHints(ts);
   279  
   280      if (cm.somethingSelected()) return;
   281      var state = cm.getTokenAt(cm.getCursor()).state;
   282      var inner = CodeMirror.innerMode(cm.getMode(), state);
   283      if (inner.mode.name != "javascript") return;
   284      var lex = inner.state.lexical;
   285      if (lex.info != "call") return;
   286  
   287      var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize");
   288      for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
   289        var str = cm.getLine(line), extra = 0;
   290        for (var pos = 0;;) {
   291          var tab = str.indexOf("\t", pos);
   292          if (tab == -1) break;
   293          extra += tabSize - (tab + extra) % tabSize - 1;
   294          pos = tab + 1;
   295        }
   296        ch = lex.column - extra;
   297        if (str.charAt(ch) == "(") {found = true; break;}
   298      }
   299      if (!found) return;
   300  
   301      var start = Pos(line, ch);
   302      var cache = ts.cachedArgHints;
   303      if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
   304        return showArgHints(ts, cm, argPos);
   305  
   306      ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
   307        if (error || !data.type || !(/^fn\(/).test(data.type)) return;
   308        ts.cachedArgHints = {
   309          start: pos,
   310          type: parseFnType(data.type),
   311          name: data.exprName || data.name || "fn",
   312          guess: data.guess,
   313          doc: cm.getDoc()
   314        };
   315        showArgHints(ts, cm, argPos);
   316      });
   317    }
   318  
   319    function showArgHints(ts, cm, pos) {
   320      closeArgHints(ts);
   321  
   322      var cache = ts.cachedArgHints, tp = cache.type;
   323      var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
   324                    elt("span", cls + "fname", cache.name), "(");
   325      for (var i = 0; i < tp.args.length; ++i) {
   326        if (i) tip.appendChild(document.createTextNode(", "));
   327        var arg = tp.args[i];
   328        tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?"));
   329        if (arg.type != "?") {
   330          tip.appendChild(document.createTextNode(":\u00a0"));
   331          tip.appendChild(elt("span", cls + "type", arg.type));
   332        }
   333      }
   334      tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
   335      if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
   336      var place = cm.cursorCoords(null, "page");
   337      ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
   338    }
   339  
   340    function parseFnType(text) {
   341      var args = [], pos = 3;
   342  
   343      function skipMatching(upto) {
   344        var depth = 0, start = pos;
   345        for (;;) {
   346          var next = text.charAt(pos);
   347          if (upto.test(next) && !depth) return text.slice(start, pos);
   348          if (/[{\[\(]/.test(next)) ++depth;
   349          else if (/[}\]\)]/.test(next)) --depth;
   350          ++pos;
   351        }
   352      }
   353  
   354      // Parse arguments
   355      if (text.charAt(pos) != ")") for (;;) {
   356        var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
   357        if (name) {
   358          pos += name[0].length;
   359          name = name[1];
   360        }
   361        args.push({name: name, type: skipMatching(/[\),]/)});
   362        if (text.charAt(pos) == ")") break;
   363        pos += 2;
   364      }
   365  
   366      var rettype = text.slice(pos).match(/^\) -> (.*)$/);
   367  
   368      return {args: args, rettype: rettype && rettype[1]};
   369    }
   370  
   371    // Moving to the definition of something
   372  
   373    function jumpToDef(ts, cm) {
   374      function inner(varName) {
   375        var req = {type: "definition", variable: varName || null};
   376        var doc = findDoc(ts, cm.getDoc());
   377        ts.server.request(buildRequest(ts, doc, req), function(error, data) {
   378          if (error) return showError(ts, cm, error);
   379          if (!data.file && data.url) { window.open(data.url); return; }
   380  
   381          if (data.file) {
   382            var localDoc = ts.docs[data.file], found;
   383            if (localDoc && (found = findContext(localDoc.doc, data))) {
   384              ts.jumpStack.push({file: doc.name,
   385                                 start: cm.getCursor("from"),
   386                                 end: cm.getCursor("to")});
   387              moveTo(ts, doc, localDoc, found.start, found.end);
   388              return;
   389            }
   390          }
   391          showError(ts, cm, "Could not find a definition.");
   392        });
   393      }
   394  
   395      if (!atInterestingExpression(cm))
   396        dialog(cm, "Jump to variable", function(name) { if (name) inner(name); });
   397      else
   398        inner();
   399    }
   400  
   401    function jumpBack(ts, cm) {
   402      var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file];
   403      if (!doc) return;
   404      moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end);
   405    }
   406  
   407    function moveTo(ts, curDoc, doc, start, end) {
   408      doc.doc.setSelection(start, end);
   409      if (curDoc != doc && ts.options.switchToDoc) {
   410        closeArgHints(ts);
   411        ts.options.switchToDoc(doc.name, doc.doc);
   412      }
   413    }
   414  
   415    // The {line,ch} representation of positions makes this rather awkward.
   416    function findContext(doc, data) {
   417      var before = data.context.slice(0, data.contextOffset).split("\n");
   418      var startLine = data.start.line - (before.length - 1);
   419      var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length);
   420  
   421      var text = doc.getLine(startLine).slice(start.ch);
   422      for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur)
   423        text += "\n" + doc.getLine(cur);
   424      if (text.slice(0, data.context.length) == data.context) return data;
   425  
   426      var cursor = doc.getSearchCursor(data.context, 0, false);
   427      var nearest, nearestDist = Infinity;
   428      while (cursor.findNext()) {
   429        var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000;
   430        if (!dist) dist = Math.abs(from.ch - start.ch);
   431        if (dist < nearestDist) { nearest = from; nearestDist = dist; }
   432      }
   433      if (!nearest) return null;
   434  
   435      if (before.length == 1)
   436        nearest.ch += before[0].length;
   437      else
   438        nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length);
   439      if (data.start.line == data.end.line)
   440        var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch));
   441      else
   442        var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch);
   443      return {start: nearest, end: end};
   444    }
   445  
   446    function atInterestingExpression(cm) {
   447      var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
   448      if (tok.start < pos.ch && tok.type == "comment") return false;
   449      return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
   450    }
   451  
   452    // Variable renaming
   453  
   454    function rename(ts, cm) {
   455      var token = cm.getTokenAt(cm.getCursor());
   456      if (!/\w/.test(token.string)) return showError(ts, cm, "Not at a variable");
   457      dialog(cm, "New name for " + token.string, function(newName) {
   458        ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
   459          if (error) return showError(ts, cm, error);
   460          applyChanges(ts, data.changes);
   461        });
   462      });
   463    }
   464  
   465    function selectName(ts, cm) {
   466      var name = findDoc(ts, cm.doc).name;
   467      ts.request(cm, {type: "refs"}, function(error, data) {
   468        if (error) return showError(ts, cm, error);
   469        var ranges = [], cur = 0;
   470        var curPos = cm.getCursor();
   471        for (var i = 0; i < data.refs.length; i++) {
   472          var ref = data.refs[i];
   473          if (ref.file == name) {
   474            ranges.push({anchor: ref.start, head: ref.end});
   475            if (cmpPos(curPos, ref.start) >= 0 && cmpPos(curPos, ref.end) <= 0)
   476              cur = ranges.length - 1;
   477          }
   478        }
   479        cm.setSelections(ranges, cur);
   480      });
   481    }
   482  
   483    var nextChangeOrig = 0;
   484    function applyChanges(ts, changes) {
   485      var perFile = Object.create(null);
   486      for (var i = 0; i < changes.length; ++i) {
   487        var ch = changes[i];
   488        (perFile[ch.file] || (perFile[ch.file] = [])).push(ch);
   489      }
   490      for (var file in perFile) {
   491        var known = ts.docs[file], chs = perFile[file];;
   492        if (!known) continue;
   493        chs.sort(function(a, b) { return cmpPos(b.start, a.start); });
   494        var origin = "*rename" + (++nextChangeOrig);
   495        for (var i = 0; i < chs.length; ++i) {
   496          var ch = chs[i];
   497          known.doc.replaceRange(ch.text, ch.start, ch.end, origin);
   498        }
   499      }
   500    }
   501  
   502    // Generic request-building helper
   503  
   504    function buildRequest(ts, doc, query, pos) {
   505      var files = [], offsetLines = 0, allowFragments = !query.fullDocs;
   506      if (!allowFragments) delete query.fullDocs;
   507      if (typeof query == "string") query = {type: query};
   508      query.lineCharPositions = true;
   509      if (query.end == null) {
   510        query.end = pos || doc.doc.getCursor("end");
   511        if (doc.doc.somethingSelected())
   512          query.start = doc.doc.getCursor("start");
   513      }
   514      var startPos = query.start || query.end;
   515  
   516      if (doc.changed) {
   517        if (doc.doc.lineCount() > bigDoc && allowFragments !== false &&
   518            doc.changed.to - doc.changed.from < 100 &&
   519            doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
   520          files.push(getFragmentAround(doc, startPos, query.end));
   521          query.file = "#0";
   522          var offsetLines = files[0].offsetLines;
   523          if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
   524          query.end = Pos(query.end.line - offsetLines, query.end.ch);
   525        } else {
   526          files.push({type: "full",
   527                      name: doc.name,
   528                      text: docValue(ts, doc)});
   529          query.file = doc.name;
   530          doc.changed = null;
   531        }
   532      } else {
   533        query.file = doc.name;
   534      }
   535      for (var name in ts.docs) {
   536        var cur = ts.docs[name];
   537        if (cur.changed && cur != doc) {
   538          files.push({type: "full", name: cur.name, text: docValue(ts, cur)});
   539          cur.changed = null;
   540        }
   541      }
   542  
   543      return {query: query, files: files};
   544    }
   545  
   546    function getFragmentAround(data, start, end) {
   547      var doc = data.doc;
   548      var minIndent = null, minLine = null, endLine, tabSize = 4;
   549      for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
   550        var line = doc.getLine(p), fn = line.search(/\bfunction\b/);
   551        if (fn < 0) continue;
   552        var indent = CodeMirror.countColumn(line, null, tabSize);
   553        if (minIndent != null && minIndent <= indent) continue;
   554        minIndent = indent;
   555        minLine = p;
   556      }
   557      if (minLine == null) minLine = min;
   558      var max = Math.min(doc.lastLine(), end.line + 20);
   559      if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize))
   560        endLine = max;
   561      else for (endLine = end.line + 1; endLine < max; ++endLine) {
   562        var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize);
   563        if (indent <= minIndent) break;
   564      }
   565      var from = Pos(minLine, 0);
   566  
   567      return {type: "part",
   568              name: data.name,
   569              offsetLines: from.line,
   570              text: doc.getRange(from, Pos(endLine, 0))};
   571    }
   572  
   573    // Generic utilities
   574  
   575    var cmpPos = CodeMirror.cmpPos;
   576  
   577    function elt(tagname, cls /*, ... elts*/) {
   578      var e = document.createElement(tagname);
   579      if (cls) e.className = cls;
   580      for (var i = 2; i < arguments.length; ++i) {
   581        var elt = arguments[i];
   582        if (typeof elt == "string") elt = document.createTextNode(elt);
   583        e.appendChild(elt);
   584      }
   585      return e;
   586    }
   587  
   588    function dialog(cm, text, f) {
   589      if (cm.openDialog)
   590        cm.openDialog(text + ": <input type=text>", f);
   591      else
   592        f(prompt(text, ""));
   593    }
   594  
   595    // Tooltips
   596  
   597    function tempTooltip(cm, content, ts) {
   598      if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
   599      var where = cm.cursorCoords();
   600      var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
   601      function maybeClear() {
   602        old = true;
   603        if (!mouseOnTip) clear();
   604      }
   605      function clear() {
   606        cm.state.ternTooltip = null;
   607        if (!tip.parentNode) return;
   608        cm.off("cursorActivity", clear);
   609        cm.off('blur', clear);
   610        cm.off('scroll', clear);
   611        fadeOut(tip);
   612      }
   613      var mouseOnTip = false, old = false;
   614      CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
   615      CodeMirror.on(tip, "mouseout", function(e) {
   616        if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
   617          if (old) clear();
   618          else mouseOnTip = false;
   619        }
   620      });
   621      setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700);
   622      cm.on("cursorActivity", clear);
   623      cm.on('blur', clear);
   624      cm.on('scroll', clear);
   625    }
   626  
   627    function makeTooltip(x, y, content) {
   628      var node = elt("div", cls + "tooltip", content);
   629      node.style.left = x + "px";
   630      node.style.top = y + "px";
   631      document.body.appendChild(node);
   632      return node;
   633    }
   634  
   635    function remove(node) {
   636      var p = node && node.parentNode;
   637      if (p) p.removeChild(node);
   638    }
   639  
   640    function fadeOut(tooltip) {
   641      tooltip.style.opacity = "0";
   642      setTimeout(function() { remove(tooltip); }, 1100);
   643    }
   644  
   645    function showError(ts, cm, msg) {
   646      if (ts.options.showError)
   647        ts.options.showError(cm, msg);
   648      else
   649        tempTooltip(cm, String(msg), ts);
   650    }
   651  
   652    function closeArgHints(ts) {
   653      if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
   654    }
   655  
   656    function docValue(ts, doc) {
   657      var val = doc.doc.getValue();
   658      if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
   659      return val;
   660    }
   661  
   662    // Worker wrapper
   663  
   664    function WorkerServer(ts) {
   665      var worker = ts.worker = new Worker(ts.options.workerScript);
   666      worker.postMessage({type: "init",
   667                          defs: ts.options.defs,
   668                          plugins: ts.options.plugins,
   669                          scripts: ts.options.workerDeps});
   670      var msgId = 0, pending = {};
   671  
   672      function send(data, c) {
   673        if (c) {
   674          data.id = ++msgId;
   675          pending[msgId] = c;
   676        }
   677        worker.postMessage(data);
   678      }
   679      worker.onmessage = function(e) {
   680        var data = e.data;
   681        if (data.type == "getFile") {
   682          getFile(ts, data.name, function(err, text) {
   683            send({type: "getFile", err: String(err), text: text, id: data.id});
   684          });
   685        } else if (data.type == "debug") {
   686          window.console.log(data.message);
   687        } else if (data.id && pending[data.id]) {
   688          pending[data.id](data.err, data.body);
   689          delete pending[data.id];
   690        }
   691      };
   692      worker.onerror = function(e) {
   693        for (var id in pending) pending[id](e);
   694        pending = {};
   695      };
   696  
   697      this.addFile = function(name, text) { send({type: "add", name: name, text: text}); };
   698      this.delFile = function(name) { send({type: "del", name: name}); };
   699      this.request = function(body, c) { send({type: "req", body: body}, c); };
   700    }
   701  });