PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 2.0.4
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v2.0.4
4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 All 340 releases
profile-builder / assets / lib / codemirror / addon / tern / tern.js

tern.js in User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor 2.0.4, at assets/lib/codemirror/addon/tern/tern.js

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