mirror of
https://github.com/adobe/brackets.git
synced 2024-11-20 18:02:54 +01:00
Merge pull request #3439 from adobe-research/master
Switch js code hinting to be powered by tern.
This commit is contained in:
commit
372ce5af1f
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -10,3 +10,9 @@
|
||||
[submodule "src/thirdparty/mustache"]
|
||||
path = src/thirdparty/mustache
|
||||
url = https://github.com/janl/mustache.js.git
|
||||
[submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/tern"]
|
||||
path = src/extensions/default/JavaScriptCodeHints/thirdparty/tern
|
||||
url = https://github.com/marijnh/tern.git
|
||||
[submodule "src/extensions/default/JavaScriptCodeHints/thirdparty/acorn"]
|
||||
path = src/extensions/default/JavaScriptCodeHints/thirdparty/acorn
|
||||
url = https://github.com/marijnh/acorn.git
|
||||
|
@ -27,10 +27,15 @@
|
||||
define(function (require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var LANGUAGE_ID = "javascript",
|
||||
SCOPE_MSG_TYPE = "outerScope",
|
||||
SINGLE_QUOTE = "\'",
|
||||
DOUBLE_QUOTE = "\"";
|
||||
var LANGUAGE_ID = "javascript",
|
||||
SINGLE_QUOTE = "'",
|
||||
DOUBLE_QUOTE = "\"",
|
||||
TERN_INIT_MSG = "Init",
|
||||
TERN_JUMPTODEF_MSG = "JumptoDef",
|
||||
TERN_COMPLETIONS_MSG = "Completions",
|
||||
TERN_GET_FILE_MSG = "GetFile",
|
||||
TERN_GET_PROPERTIES_MSG = "Properties",
|
||||
TERN_CALLED_FUNC_TYPE_MSG = "FunctionType";
|
||||
|
||||
/**
|
||||
* Create a hint token with name value that occurs at the given list of
|
||||
@ -57,9 +62,7 @@ define(function (require, exports, module) {
|
||||
* @return {boolean} - could key be a valid identifier?
|
||||
*/
|
||||
function maybeIdentifier(key) {
|
||||
return (/[0-9a-z_.\$]/i).test(key) ||
|
||||
(key.indexOf(SINGLE_QUOTE) === 0) ||
|
||||
(key.indexOf(DOUBLE_QUOTE) === 0);
|
||||
return (/[0-9a-z_\$]/i).test(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,7 +81,18 @@ define(function (require, exports, module) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if hints should be displayed for the given key.
|
||||
*
|
||||
* @param {string} key - key entered by the user
|
||||
* @return {boolean} true if the hints should be shown for the key,
|
||||
* false otherwise.
|
||||
*/
|
||||
function hintableKey(key) {
|
||||
return (key === null || key === "." || maybeIdentifier(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide a path into directory and filename parts
|
||||
*
|
||||
@ -89,7 +103,7 @@ define(function (require, exports, module) {
|
||||
function splitPath(path) {
|
||||
var index = path.lastIndexOf("/"),
|
||||
dir = path.substring(0, index),
|
||||
file = path.substring(index, path.length);
|
||||
file = path.substring(index + 1, path.length);
|
||||
|
||||
return {dir: dir, file: file };
|
||||
}
|
||||
@ -106,93 +120,36 @@ define(function (require, exports, module) {
|
||||
return name + "." + EVENT_TAG;
|
||||
}
|
||||
|
||||
/*
|
||||
* Annotate list of identifiers with their scope level.
|
||||
*
|
||||
* @param {Array.<Object>} identifiers - list of identifier tokens to be
|
||||
* annotated
|
||||
* @param {Scope} scope - scope object used to determine the scope level of
|
||||
* each identifier token in the previous list.
|
||||
* @return {Array.<Object>} - the input array; to each object in the array a
|
||||
* new level {number} property has been added to indicate its scope
|
||||
* level.
|
||||
*/
|
||||
function annotateWithScope(identifiers, scope) {
|
||||
return identifiers.map(function (t) {
|
||||
var level = scope.contains(t.value);
|
||||
|
||||
if (level >= 0) {
|
||||
t.level = level;
|
||||
} else {
|
||||
t.level = -1;
|
||||
}
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Annotate a list of properties with their association level
|
||||
*
|
||||
* @param {Array.<Object>} properties - list of property tokens
|
||||
* @param {Association} association - an object that maps property
|
||||
* names to the number of times it occurs in a particular context
|
||||
* @return {Array.<Object>} - the input array; to each object in the array a
|
||||
* new level {number} property has been added to indicate the number
|
||||
* of times the property has occurred in the association context.
|
||||
*/
|
||||
function annotateWithAssociation(properties, association) {
|
||||
return properties.map(function (t) {
|
||||
if (association[t.value] > 0) {
|
||||
t.level = 0;
|
||||
}
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Annotate a list of tokens as being global variables
|
||||
*
|
||||
* @param {Array.<Object>} globals - list of identifier tokens
|
||||
* @return {Array.<Object>} - the input array; to each object in the array a
|
||||
* new global {boolean} property has been added to indicate that it is
|
||||
* a global variable.
|
||||
*/
|
||||
function annotateGlobals(globals) {
|
||||
return globals.map(function (t) {
|
||||
t.global = true;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Annotate a list of tokens as literals of a particular kind;
|
||||
* if string literals, annotate with an appropriate delimiter.
|
||||
*
|
||||
* if string literals, annotate with an appropriate delimiter.
|
||||
*
|
||||
* @param {Array.<Object>} literals - list of hint tokens
|
||||
* @param {string} kind - the kind of literals in the list (e.g., "string")
|
||||
* @return {Array.<Object>} - the input array; to each object in the array a
|
||||
* new literal {boolean} property has been added to indicate that it
|
||||
* is a literal hint, and also a new kind {string} property to indicate
|
||||
* the literal kind. For string literals, a delimiter property is also
|
||||
* added to indicate what the default delimiter should be (viz. a
|
||||
* added to indicate what the default delimiter should be (viz. a
|
||||
* single or double quotation mark).
|
||||
*/
|
||||
function annotateLiterals(literals, kind) {
|
||||
return literals.map(function (t) {
|
||||
t.literal = true;
|
||||
t.kind = kind;
|
||||
t.origin = "ecma5";
|
||||
if (kind === "string") {
|
||||
if (/[\\\\]*[^\\]"/.test(t.value)) {
|
||||
t.delimeter = SINGLE_QUOTE;
|
||||
t.delimiter = SINGLE_QUOTE;
|
||||
} else {
|
||||
t.delimeter = DOUBLE_QUOTE;
|
||||
t.delimiter = DOUBLE_QUOTE;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* Annotate a list of tokens as keywords
|
||||
*
|
||||
* @param {Array.<Object>} keyword - list of keyword tokens
|
||||
@ -203,31 +160,11 @@ define(function (require, exports, module) {
|
||||
function annotateKeywords(keywords) {
|
||||
return keywords.map(function (t) {
|
||||
t.keyword = true;
|
||||
t.origin = "ecma5";
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Annotate a list of tokens with a path name
|
||||
*
|
||||
* @param {Array.<Object>} tokens - list of hint tokens
|
||||
* @param {string} dir - directory with which to annotate the hints
|
||||
* @param {string} file - file name with which to annotate the hints
|
||||
* @return {Array.<Object>} - the input array; to each object in the array a
|
||||
* new path {string} property has been added, equal to dir + file.
|
||||
*/
|
||||
function annotateWithPath(tokens, dir, file) {
|
||||
var path = dir + file;
|
||||
|
||||
return tokens.map(function (t) {
|
||||
t.path = path;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO 1: The definitions below should be defined in a separate JSON file.
|
||||
// TODO 2: Add properties and associations for the builtin globals.
|
||||
|
||||
var KEYWORD_NAMES = [
|
||||
"break", "case", "catch", "continue", "debugger", "default", "delete",
|
||||
"do", "else", "finally", "for", "function", "if", "in", "instanceof",
|
||||
@ -247,141 +184,22 @@ define(function (require, exports, module) {
|
||||
}),
|
||||
LITERALS = annotateLiterals(LITERAL_TOKENS);
|
||||
|
||||
var JSL_GLOBAL_NAMES = [
|
||||
"clearInterval", "clearTimeout", "document", "event", "frames",
|
||||
"history", "Image", "location", "name", "navigator", "Option",
|
||||
"parent", "screen", "setInterval", "setTimeout", "window",
|
||||
"XMLHttpRequest", "alert", "confirm", "console", "Debug", "opera",
|
||||
"prompt", "WSH", "Buffer", "exports", "global", "module", "process",
|
||||
"querystring", "require", "__filename", "__dirname", "defineClass",
|
||||
"deserialize", "gc", "help", "load", "loadClass", "print", "quit",
|
||||
"readFile", "readUrl", "runCommand", "seal", "serialize", "spawn",
|
||||
"sync", "toint32", "version", "ActiveXObject", "CScript", "Enumerator",
|
||||
"System", "VBArray", "WScript"
|
||||
],
|
||||
JSL_GLOBAL_TOKENS = annotateGlobals(JSL_GLOBAL_NAMES.map(function (t) {
|
||||
return makeToken(t, []);
|
||||
})),
|
||||
JSL_GLOBALS = JSL_GLOBAL_TOKENS.reduce(function (prev, curr) {
|
||||
prev[curr.value] = curr;
|
||||
return prev;
|
||||
}, {}); // builds an object from the array of tokens.
|
||||
|
||||
// Predefined sets of globals as defined by JSLint
|
||||
var JSL_GLOBALS_BROWSER = [
|
||||
JSL_GLOBALS.clearInterval,
|
||||
JSL_GLOBALS.clearTimeout,
|
||||
JSL_GLOBALS.document,
|
||||
JSL_GLOBALS.event,
|
||||
JSL_GLOBALS.frames,
|
||||
JSL_GLOBALS.history,
|
||||
JSL_GLOBALS.Image,
|
||||
JSL_GLOBALS.location,
|
||||
JSL_GLOBALS.name,
|
||||
JSL_GLOBALS.navigator,
|
||||
JSL_GLOBALS.Option,
|
||||
JSL_GLOBALS.parent,
|
||||
JSL_GLOBALS.screen,
|
||||
JSL_GLOBALS.setInterval,
|
||||
JSL_GLOBALS.setTimeout,
|
||||
JSL_GLOBALS.window,
|
||||
JSL_GLOBALS.XMLHttpRequest
|
||||
],
|
||||
JSL_GLOBALS_DEVEL = [
|
||||
JSL_GLOBALS.alert,
|
||||
JSL_GLOBALS.confirm,
|
||||
JSL_GLOBALS.console,
|
||||
JSL_GLOBALS.Debug,
|
||||
JSL_GLOBALS.opera,
|
||||
JSL_GLOBALS.prompt,
|
||||
JSL_GLOBALS.WSH
|
||||
],
|
||||
JSL_GLOBALS_NODE = [
|
||||
JSL_GLOBALS.Buffer,
|
||||
JSL_GLOBALS.clearInterval,
|
||||
JSL_GLOBALS.clearTimeout,
|
||||
JSL_GLOBALS.console,
|
||||
JSL_GLOBALS.exports,
|
||||
JSL_GLOBALS.global,
|
||||
JSL_GLOBALS.module,
|
||||
JSL_GLOBALS.process,
|
||||
JSL_GLOBALS.querystring,
|
||||
JSL_GLOBALS.require,
|
||||
JSL_GLOBALS.setInterval,
|
||||
JSL_GLOBALS.setTimeout,
|
||||
JSL_GLOBALS.__filename,
|
||||
JSL_GLOBALS.__dirname
|
||||
],
|
||||
JSL_GLOBALS_RHINO = [
|
||||
JSL_GLOBALS.defineClass,
|
||||
JSL_GLOBALS.deserialize,
|
||||
JSL_GLOBALS.gc,
|
||||
JSL_GLOBALS.help,
|
||||
JSL_GLOBALS.load,
|
||||
JSL_GLOBALS.loadClass,
|
||||
JSL_GLOBALS.print,
|
||||
JSL_GLOBALS.quit,
|
||||
JSL_GLOBALS.readFile,
|
||||
JSL_GLOBALS.readUrl,
|
||||
JSL_GLOBALS.runCommand,
|
||||
JSL_GLOBALS.seal,
|
||||
JSL_GLOBALS.serialize,
|
||||
JSL_GLOBALS.spawn,
|
||||
JSL_GLOBALS.sync,
|
||||
JSL_GLOBALS.toint32,
|
||||
JSL_GLOBALS.version
|
||||
],
|
||||
JSL_GLOBALS_WINDOWS = [
|
||||
JSL_GLOBALS.ActiveXObject,
|
||||
JSL_GLOBALS.CScript,
|
||||
JSL_GLOBALS.Debug,
|
||||
JSL_GLOBALS.Enumerator,
|
||||
JSL_GLOBALS.System,
|
||||
JSL_GLOBALS.VBArray,
|
||||
JSL_GLOBALS.WScript,
|
||||
JSL_GLOBALS.WSH
|
||||
];
|
||||
|
||||
var JSL_GLOBAL_DEFS = {
|
||||
browser : JSL_GLOBALS_BROWSER,
|
||||
devel : JSL_GLOBALS_DEVEL,
|
||||
node : JSL_GLOBALS_NODE,
|
||||
rhino : JSL_GLOBALS_RHINO,
|
||||
windows : JSL_GLOBALS_WINDOWS
|
||||
};
|
||||
|
||||
var BUILTIN_GLOBAL_NAMES = [
|
||||
"Array", "Boolean", "Date", "Function", "Iterator", "Number", "Object",
|
||||
"RegExp", "String", "ArrayBuffer", "DataView", "Float32Array",
|
||||
"Float64Array", "Int16Array", "Int32Array", "Int8Array", "Uint16Array",
|
||||
"Uint32Array", "Uint8Array", "Uint8ClampedArray", "Error", "EvalError",
|
||||
"InternalError", "RangeError", "ReferenceError", "StopIteration",
|
||||
"SyntaxError", "TypeError", "URIError", "decodeURI",
|
||||
"decodeURIComponent", "encodeURI", "encodeURIComponent", "eval",
|
||||
"isFinite", "isNaN", "parseFloat", "parseInt", "uneval", "Infinity",
|
||||
"JSON", "Math", "NaN"
|
||||
],
|
||||
BUILTIN_GLOBAL_TOKENS = BUILTIN_GLOBAL_NAMES.map(function (t) {
|
||||
return makeToken(t, []);
|
||||
}),
|
||||
BUILTIN_GLOBALS = annotateGlobals(BUILTIN_GLOBAL_TOKENS);
|
||||
|
||||
exports.makeToken = makeToken;
|
||||
exports.hintable = hintable;
|
||||
exports.hintableKey = hintableKey;
|
||||
exports.maybeIdentifier = maybeIdentifier;
|
||||
exports.splitPath = splitPath;
|
||||
exports.eventName = eventName;
|
||||
exports.annotateWithPath = annotateWithPath;
|
||||
exports.annotateLiterals = annotateLiterals;
|
||||
exports.annotateGlobals = annotateGlobals;
|
||||
exports.annotateWithScope = annotateWithScope;
|
||||
exports.annotateWithAssociation = annotateWithAssociation;
|
||||
exports.JSL_GLOBAL_DEFS = JSL_GLOBAL_DEFS;
|
||||
exports.BUILTIN_GLOBALS = BUILTIN_GLOBALS;
|
||||
exports.KEYWORDS = KEYWORDS;
|
||||
exports.LITERALS = LITERALS;
|
||||
exports.LANGUAGE_ID = LANGUAGE_ID;
|
||||
exports.SCOPE_MSG_TYPE = SCOPE_MSG_TYPE;
|
||||
exports.SINGLE_QUOTE = SINGLE_QUOTE;
|
||||
exports.DOUBLE_QUOTE = DOUBLE_QUOTE;
|
||||
exports.TERN_JUMPTODEF_MSG = TERN_JUMPTODEF_MSG;
|
||||
exports.TERN_COMPLETIONS_MSG = TERN_COMPLETIONS_MSG;
|
||||
exports.TERN_INIT_MSG = TERN_INIT_MSG;
|
||||
exports.TERN_GET_FILE_MSG = TERN_GET_FILE_MSG;
|
||||
exports.TERN_GET_PROPERTIES_MSG = TERN_GET_PROPERTIES_MSG;
|
||||
exports.TERN_CALLED_FUNC_TYPE_MSG = TERN_CALLED_FUNC_TYPE_MSG;
|
||||
});
|
||||
|
@ -1,682 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
|
||||
/*global define */
|
||||
|
||||
define(function (require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Performs a binary search among an array of scope objects for one with a
|
||||
* range that contains pos. The array must be non-empty and sorted
|
||||
* ascending according to the objects' (disjoint) ranges.
|
||||
*
|
||||
* @param {Array.<Object>} arr - the sorted array of scope objects to
|
||||
* search
|
||||
* @param {number} pos - the position to search for in arr
|
||||
* @return {Object} - the scope object containing pos
|
||||
*/
|
||||
function binaryRangeSearch(arr, pos) {
|
||||
var low = 0,
|
||||
high = arr.length,
|
||||
middle = Math.floor(high / 2);
|
||||
|
||||
// binary search for the position among the sorted ranges
|
||||
while (low < middle && middle < high) {
|
||||
if (arr[middle].range.end < pos) {
|
||||
low = middle;
|
||||
middle += Math.floor((high - middle) / 2);
|
||||
} else if (arr[middle].range.start > pos) {
|
||||
high = middle;
|
||||
middle = low + Math.floor((middle - low) / 2);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return arr[middle];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a scope object from AST tree as a child of the given parent.
|
||||
*
|
||||
* @constructor
|
||||
* @param {AST} tree - an AST as described at
|
||||
https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
|
||||
* @param {?Scope=} parent - the (optional) parent of the new Scope
|
||||
*/
|
||||
function Scope(tree, parent) {
|
||||
|
||||
/*
|
||||
* Given a member expression, try to add a target-property association
|
||||
* to the given scope.
|
||||
*
|
||||
* @param {AST} object - the lookup object
|
||||
* @param {AST} property - the property being looked up
|
||||
* @param {Scope} parent - the Scope object in which the association
|
||||
* occurs.
|
||||
*/
|
||||
function _buildAssociations(object, property, parent) {
|
||||
if (property.type === "Identifier") {
|
||||
if (object.type === "Identifier") {
|
||||
parent.addAssociation(object, property);
|
||||
} else if (object.type === "MemberExpression") {
|
||||
if (object.computed === false) {
|
||||
_buildAssociations(object.property, property, parent);
|
||||
}
|
||||
} else if (object.type === "CallExpression") {
|
||||
_buildAssociations(object.callee, property, parent);
|
||||
} else if (object.type === "ThisExpression") {
|
||||
object.name = "this";
|
||||
parent.addAssociation(object, property);
|
||||
} else {
|
||||
// most likely a literal
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Because we restrict to non-computed property lookups, this
|
||||
// should be unreachable
|
||||
throw "Expected identifier but found " + property.type;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk a given AST and add scope information to a given parent scope,
|
||||
* including new child Scope objects.
|
||||
*
|
||||
* @param {AST} tree - the AST from which the Scope is constructed
|
||||
* @param {Scope} parent - the parent of the Scope to be constructed
|
||||
*/
|
||||
function _buildScope(tree, parent) {
|
||||
var child;
|
||||
|
||||
if (tree === undefined || tree === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tree.type) {
|
||||
case "Program":
|
||||
case "BlockStatement":
|
||||
tree.body.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
break;
|
||||
|
||||
case "FunctionDeclaration":
|
||||
parent.addDeclaration(tree.id);
|
||||
_buildScope(tree.id, parent);
|
||||
child = new Scope(tree, parent);
|
||||
child.addAllDeclarations(tree.params);
|
||||
tree.params.forEach(function (t) {
|
||||
_buildScope(t, child);
|
||||
});
|
||||
parent.addChildScope(child);
|
||||
_buildScope(tree.body, child);
|
||||
break;
|
||||
|
||||
case "VariableDeclaration":
|
||||
// FIXME handle let scoping
|
||||
tree.declarations.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
break;
|
||||
|
||||
case "VariableDeclarator":
|
||||
parent.addDeclaration(tree.id);
|
||||
_buildScope(tree.id, parent);
|
||||
if (tree.init !== null) {
|
||||
_buildScope(tree.init, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ExpressionStatement":
|
||||
_buildScope(tree.expression, parent);
|
||||
break;
|
||||
|
||||
case "SwitchStatement":
|
||||
_buildScope(tree.discriminant, parent);
|
||||
if (tree.cases) {
|
||||
tree.cases.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "SwitchCase":
|
||||
tree.consequent.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
if (tree.test) {
|
||||
_buildScope(tree.test, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "TryStatement":
|
||||
tree.handlers.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
_buildScope(tree.block, parent);
|
||||
if (tree.finalizer) {
|
||||
_buildScope(tree.finalizer, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ThrowStatement":
|
||||
_buildScope(tree.argument, parent);
|
||||
break;
|
||||
|
||||
case "WithStatement":
|
||||
_buildScope(tree.object, parent);
|
||||
_buildScope(tree.body, parent);
|
||||
break;
|
||||
|
||||
case "CatchClause":
|
||||
if (tree.guard) {
|
||||
_buildScope(tree.guard, parent);
|
||||
}
|
||||
// FIXME: Is this the correct way to handle catch?
|
||||
child = new Scope(tree, parent);
|
||||
child.addDeclaration(tree.param);
|
||||
_buildScope(tree.param, child);
|
||||
parent.addChildScope(child);
|
||||
_buildScope(tree.body, child);
|
||||
break;
|
||||
|
||||
case "ReturnStatement":
|
||||
if (tree.argument) {
|
||||
_buildScope(tree.argument, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ForStatement":
|
||||
_buildScope(tree.body, parent);
|
||||
if (tree.init) {
|
||||
_buildScope(tree.init, parent);
|
||||
}
|
||||
if (tree.test) {
|
||||
_buildScope(tree.test, parent);
|
||||
}
|
||||
if (tree.update) {
|
||||
_buildScope(tree.update, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ForInStatement":
|
||||
_buildScope(tree.left, parent);
|
||||
_buildScope(tree.right, parent);
|
||||
_buildScope(tree.body, parent);
|
||||
break;
|
||||
|
||||
case "LabeledStatement":
|
||||
_buildScope(tree.body, parent);
|
||||
break;
|
||||
|
||||
case "BreakStatement":
|
||||
case "ContinueStatement":
|
||||
if (tree.label) {
|
||||
_buildScope(tree.label, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "UpdateExpression":
|
||||
case "UnaryExpression":
|
||||
_buildScope(tree.argument, parent);
|
||||
break;
|
||||
|
||||
case "IfStatement":
|
||||
case "ConditionalExpression":
|
||||
_buildScope(tree.test, parent);
|
||||
_buildScope(tree.consequent, parent);
|
||||
if (tree.alternate) {
|
||||
_buildScope(tree.alternate, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "WhileStatement":
|
||||
case "DoWhileStatement":
|
||||
_buildScope(tree.test, parent);
|
||||
_buildScope(tree.body, parent);
|
||||
break;
|
||||
|
||||
case "SequenceExpression":
|
||||
tree.expressions.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
break;
|
||||
|
||||
case "ObjectExpression":
|
||||
tree.properties.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
break;
|
||||
|
||||
case "ArrayExpression":
|
||||
tree.elements.forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
break;
|
||||
|
||||
case "NewExpression":
|
||||
if (tree["arguments"]) { // pacifies JSLint
|
||||
tree["arguments"].forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
}
|
||||
_buildScope(tree.callee, parent);
|
||||
break;
|
||||
|
||||
case "BinaryExpression":
|
||||
case "AssignmentExpression":
|
||||
case "LogicalExpression":
|
||||
_buildScope(tree.left, parent);
|
||||
_buildScope(tree.right, parent);
|
||||
break;
|
||||
|
||||
case "MemberExpression":
|
||||
_buildScope(tree.object, parent);
|
||||
_buildScope(tree.property, parent);
|
||||
if (tree.property && tree.property.type === "Identifier") {
|
||||
parent.addProperty(tree.property);
|
||||
}
|
||||
if (tree.computed === false) {
|
||||
_buildAssociations(tree.object, tree.property, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "CallExpression":
|
||||
tree["arguments"].forEach(function (t) {
|
||||
_buildScope(t, parent);
|
||||
});
|
||||
_buildScope(tree.callee, parent);
|
||||
break;
|
||||
|
||||
case "FunctionExpression":
|
||||
if (tree.id) {
|
||||
parent.addDeclaration(tree.id);
|
||||
_buildScope(tree.id, parent);
|
||||
}
|
||||
child = new Scope(tree, parent);
|
||||
parent.addChildScope(child);
|
||||
child.addAllDeclarations(tree.params);
|
||||
tree.params.forEach(function (t) {
|
||||
_buildScope(t, child);
|
||||
});
|
||||
_buildScope(tree.body, child);
|
||||
break;
|
||||
|
||||
case "Property":
|
||||
// Undocumented or Esprima-specific?
|
||||
parent.addProperty(tree.key);
|
||||
_buildScope(tree.value, parent);
|
||||
break;
|
||||
|
||||
case "Identifier":
|
||||
parent.addIdOccurrence(tree);
|
||||
break;
|
||||
|
||||
case "Literal":
|
||||
if (tree.value && typeof tree.value === "string") {
|
||||
parent.addLiteralOccurrence(tree);
|
||||
}
|
||||
break;
|
||||
|
||||
case "DebuggerStatement":
|
||||
case "EmptyStatement":
|
||||
case "ThisExpression":
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Unknown node type: " + tree.type;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent === undefined) {
|
||||
this.parent = null;
|
||||
} else {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
this.idDeclarations = {};
|
||||
this.idOccurrences = [];
|
||||
this.propOccurrences = [];
|
||||
this.associations = [];
|
||||
this.literals = [];
|
||||
|
||||
this.children = []; // disjoint ranges, ordered by range start
|
||||
this.range = {
|
||||
start: tree.range[0],
|
||||
end: tree.range[1]
|
||||
};
|
||||
|
||||
// if parent is null, walk the AST
|
||||
if (!this.parent) {
|
||||
_buildScope(tree, this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rebuild a Scope object from an object that has all the necessary data
|
||||
* but the wrong prototype. Such objects may be created as a result of
|
||||
* e.g., JSON-marshalling and unmarshalling a scope.
|
||||
*
|
||||
* @param {Object} data - an object that contains all data of a Scope object
|
||||
* but none of the methods
|
||||
* @return {Scope} - the same object with Scope methods added
|
||||
*/
|
||||
Scope.rebuild = function (data) {
|
||||
var memberName,
|
||||
member;
|
||||
|
||||
for (memberName in Scope.prototype) {
|
||||
if (Scope.prototype.hasOwnProperty(memberName)) {
|
||||
member = Scope.prototype[memberName];
|
||||
if (typeof member === "function") {
|
||||
data[memberName] = member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.children.forEach(function (child) {
|
||||
Scope.rebuild(child);
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/*
|
||||
* Add an identifier occurrence.
|
||||
*
|
||||
* @param {AST} id - an identifier AST node
|
||||
*/
|
||||
Scope.prototype.addIdOccurrence = function (id) {
|
||||
this.idOccurrences.push(id);
|
||||
};
|
||||
|
||||
/*
|
||||
* Add an identifier declaration
|
||||
*
|
||||
* @param {AST} id - an identifier AST node
|
||||
*/
|
||||
Scope.prototype.addDeclaration = function (id) {
|
||||
this.idDeclarations[id.name] = id;
|
||||
this.addIdOccurrence(id);
|
||||
};
|
||||
|
||||
/*
|
||||
* Add a list of identifier declarations
|
||||
*
|
||||
* @param {Array.<AST>} ids - a list of identifier AST nodes
|
||||
*/
|
||||
Scope.prototype.addAllDeclarations = function (ids) {
|
||||
var that = this;
|
||||
ids.forEach(function (i) {
|
||||
that.addDeclaration(i);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Add a property occurrence
|
||||
*
|
||||
* @param {AST} prop - a property AST node
|
||||
*/
|
||||
Scope.prototype.addProperty = function (prop) {
|
||||
this.propOccurrences.push(prop);
|
||||
};
|
||||
|
||||
/*
|
||||
* Add an association object
|
||||
*
|
||||
* @param {AST} obj - an identifier AST node
|
||||
* @param {AST} prop - a property AST node
|
||||
*/
|
||||
Scope.prototype.addAssociation = function (obj, prop) {
|
||||
this.associations.push({object: obj, property: prop});
|
||||
};
|
||||
|
||||
/*
|
||||
* Add a literal occurrence
|
||||
*
|
||||
* @param {AST} lit - a literal AST node
|
||||
*/
|
||||
Scope.prototype.addLiteralOccurrence = function (lit) {
|
||||
this.literals.push(lit);
|
||||
};
|
||||
|
||||
/*
|
||||
* Attach a new child scope to the current scope. Inserts the child scope
|
||||
* in range-sorted order w.r.t. the other children of the current scope.
|
||||
*
|
||||
* @param {Scope} child - the child to be added
|
||||
*/
|
||||
Scope.prototype.addChildScope = function (child) {
|
||||
var i = 0;
|
||||
|
||||
while (i < this.children.length &&
|
||||
child.range.start > this.children[i].range.end) {
|
||||
i++;
|
||||
}
|
||||
this.children.splice(i, 0, child);
|
||||
};
|
||||
|
||||
/*
|
||||
* Is the symbol declared in this scope?
|
||||
*
|
||||
* @param {string} sym - a symbol name
|
||||
* @return {boolean} - whether a symbol with that name is declared in this
|
||||
* immediate scope
|
||||
*/
|
||||
Scope.prototype.member = function (sym) {
|
||||
return Object.prototype.hasOwnProperty.call(this.idDeclarations, sym);
|
||||
};
|
||||
|
||||
/*
|
||||
* Is the symbol declared in this scope or a parent scope?
|
||||
*
|
||||
* @param {string} sym - a symbol name
|
||||
* @return {boolean} - whether a symbol with that name is declared in this
|
||||
* immediate scope or a parent scope
|
||||
*/
|
||||
Scope.prototype.contains = function (sym) {
|
||||
var depth = 0,
|
||||
child = this;
|
||||
|
||||
do {
|
||||
if (child.member(sym)) {
|
||||
return depth;
|
||||
} else {
|
||||
child = child.parent;
|
||||
depth++;
|
||||
}
|
||||
} while (child !== null);
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
* Does this scope, or its children, contain the given position?
|
||||
*
|
||||
* @param {number} pos - the position to test for inclusion in the scope
|
||||
* @return {boolean} - is the position included in the scope?
|
||||
*/
|
||||
Scope.prototype.containsPosition = function (pos) {
|
||||
return this.range.start <= pos && pos <= this.range.end;
|
||||
};
|
||||
|
||||
/*
|
||||
* Does this scope, but not its children, contain the given position?
|
||||
*
|
||||
* @param {number} pos - the position to test for inclusion in the scope
|
||||
* @return {boolean} - is the position directly included in the scope?
|
||||
*/
|
||||
Scope.prototype.containsPositionImmediate = function (pos) {
|
||||
if (this.containsPosition(pos)) {
|
||||
if (this.children.length === 0) {
|
||||
// contains the position and there are no children
|
||||
return true;
|
||||
} else {
|
||||
var child = binaryRangeSearch(this.children, pos);
|
||||
// contains the position if the nearest child does not
|
||||
return !child.containsPosition(pos);
|
||||
}
|
||||
} else {
|
||||
// does not contain the position
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Find the child scope of the current scope for a given position
|
||||
*
|
||||
* @param {number} pos - the position at which to find a child scope
|
||||
* @return {?Scope} - the child scope for the given position, or null
|
||||
* if none exists
|
||||
*/
|
||||
Scope.prototype.findChild = function (pos) {
|
||||
if (this.containsPosition(pos)) {
|
||||
if (this.children.length === 0) {
|
||||
// there are no children, so this is the most precise scope
|
||||
return this;
|
||||
} else {
|
||||
var child = binaryRangeSearch(this.children, pos);
|
||||
// the most precise scope is the most precise scope of the child,
|
||||
// unless no child contains the position, in which case this is
|
||||
// the most precise scope
|
||||
return child.findChild(pos) || this;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverse the scope down via children in pre-order.
|
||||
*
|
||||
* @param {Function} add - the Scope accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @return {Object} - the result of accumulating the current scope along
|
||||
* with all of its children
|
||||
*/
|
||||
Scope.prototype.walkDown = function (add, init) {
|
||||
var result = add(this, init);
|
||||
|
||||
this.children.forEach(function (child) {
|
||||
result = child.walkDown(add, result);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/*
|
||||
* Traverse a particular list in the scope down via children
|
||||
*
|
||||
* @param {Function} addItem - the item accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @param {string} listName - the name of a Scope property
|
||||
* @return {Object} - the result of accumulating the given property for
|
||||
* the current scope along with all of its children
|
||||
*/
|
||||
Scope.prototype.walkDownList = function (addItem, init, listName) {
|
||||
function addList(scope, init) {
|
||||
var list = scope[listName];
|
||||
return list.reduce(function (prev, curr) {
|
||||
return addItem(prev, curr);
|
||||
}, init);
|
||||
}
|
||||
|
||||
return this.walkDown(addList, init);
|
||||
};
|
||||
|
||||
/*
|
||||
* Traverse identifier occurrences in the scope down via children
|
||||
*
|
||||
* @param {Function} add - the identifier accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @return {Object} - the result of accumulating identifier occurrences
|
||||
* for the current scope along with all of its children
|
||||
*/
|
||||
Scope.prototype.walkDownIdentifiers = function (add, init) {
|
||||
return this.walkDownList(add, init, "idOccurrences");
|
||||
};
|
||||
|
||||
/*
|
||||
* Traverse property occurrences in the scope down via children
|
||||
*
|
||||
* @param {Function} add - the property accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @return {Object} - the result of of accumulating property occurrences
|
||||
* for the current scope along with all of its children
|
||||
*/
|
||||
Scope.prototype.walkDownProperties = function (add, init) {
|
||||
return this.walkDownList(add, init, "propOccurrences");
|
||||
};
|
||||
|
||||
/*
|
||||
* Traverse associations in the scope down via children
|
||||
*
|
||||
* @param {Function} add - the association accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @return {Object} - the result of of accumulating association occurrences
|
||||
* for the current scope along with all of its children
|
||||
*/
|
||||
Scope.prototype.walkDownAssociations = function (add, init) {
|
||||
return this.walkDownList(add, init, "associations");
|
||||
};
|
||||
|
||||
/*
|
||||
* Traverse literals in the scope down via children
|
||||
*
|
||||
* @param {Function} add - the literal accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @return {Object} - the result of of accumulating literal occurrences
|
||||
* for the current scope along with all of its children
|
||||
*/
|
||||
Scope.prototype.walkDownLiterals = function (add, init) {
|
||||
return this.walkDownList(add, init, "literals");
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverse the scope up via the parent
|
||||
*
|
||||
* @param {Function} add - the Scope accumulation function
|
||||
* @param {Object} init - an initial value for the accumulation function
|
||||
* @param {string} prop - the property name to combine scope information for
|
||||
* @return {Object} - the result of of accumulating the current scope along
|
||||
* with its parents
|
||||
*/
|
||||
Scope.prototype.walkUp = function (add, init, prop) {
|
||||
var scope = this,
|
||||
result = init,
|
||||
combine = function (elem) {
|
||||
result = add(result, elem);
|
||||
};
|
||||
|
||||
while (scope !== null) {
|
||||
this[prop].forEach(combine);
|
||||
scope = scope.parent;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = Scope;
|
||||
});
|
@ -36,332 +36,220 @@ define(function (require, exports, module) {
|
||||
|
||||
var DocumentManager = brackets.getModule("document/DocumentManager"),
|
||||
LanguageManager = brackets.getModule("language/LanguageManager"),
|
||||
FileUtils = brackets.getModule("file/FileUtils"),
|
||||
NativeFileSystem = brackets.getModule("file/NativeFileSystem").NativeFileSystem,
|
||||
ProjectManager = brackets.getModule("project/ProjectManager"),
|
||||
HintUtils = require("HintUtils"),
|
||||
Scope = require("Scope");
|
||||
|
||||
var pendingRequest = null, // information about a deferred scope request
|
||||
fileState = {}, // directory -> file -> state
|
||||
outerScopeWorker = (function () {
|
||||
CollectionUtils = brackets.getModule("utils/CollectionUtils"),
|
||||
HintUtils = require("HintUtils");
|
||||
|
||||
var ternEnvironment = [],
|
||||
pendingTernRequests = {},
|
||||
builtinFiles = ["ecma5.json", "browser.json", "jquery.json"],
|
||||
builtinLibraryNames = [],
|
||||
rootTernDir = null,
|
||||
projectRoot = null,
|
||||
ternPromise = null,
|
||||
resolvedFiles = {}, // file -> resolved file
|
||||
_ternWorker = (function () {
|
||||
var path = module.uri.substring(0, module.uri.lastIndexOf("/") + 1);
|
||||
return new Worker(path + "parser-worker.js");
|
||||
return new Worker(path + "tern-worker.js");
|
||||
}());
|
||||
|
||||
var MAX_TEXT_LENGTH = 1000000, // about 1MB
|
||||
MAX_FILES_IN_DIR = 100;
|
||||
|
||||
/**
|
||||
* Initialize state for a given directory and file name
|
||||
*
|
||||
* @param {string} dir - the directory name to initialize
|
||||
* @param {string} file - the file name to initialize
|
||||
/**
|
||||
* Create a new tern server.
|
||||
*/
|
||||
function initFileState(dir, file) {
|
||||
// initialize outerScope, etc. at dir
|
||||
if (!fileState.hasOwnProperty(dir)) {
|
||||
fileState[dir] = {};
|
||||
}
|
||||
|
||||
if (file !== undefined) {
|
||||
if (!fileState[dir].hasOwnProperty(file)) {
|
||||
fileState[dir][file] = {
|
||||
// global scope object for this file
|
||||
scope : null,
|
||||
|
||||
// has the file changed since the scope was updated?
|
||||
dirtyFile : true,
|
||||
|
||||
// has the scope changed since the last inner scope request?
|
||||
dirtyScope : true,
|
||||
|
||||
// is the parser worker active for this file?
|
||||
active : false,
|
||||
|
||||
// all variable and parameter names defined in this file
|
||||
identifiers : null,
|
||||
|
||||
// all property names found in this file
|
||||
properties : null,
|
||||
|
||||
// all globals defined in this file
|
||||
globals : null,
|
||||
|
||||
// all string literals found in this file
|
||||
literals : null,
|
||||
|
||||
// all context-property associations found in this file
|
||||
associations : null
|
||||
};
|
||||
}
|
||||
}
|
||||
function initTernServer(dir, files) {
|
||||
_ternWorker.postMessage({
|
||||
type : HintUtils.TERN_INIT_MSG,
|
||||
dir : dir,
|
||||
files : files,
|
||||
env : ternEnvironment
|
||||
});
|
||||
rootTernDir = dir + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file state for a given path. If just the directory is given
|
||||
* instead of the whole path, a set of file states is returned, one for
|
||||
* each (known) file in the directory.
|
||||
*
|
||||
* @param {string} dir - the directory name for which state is desired
|
||||
* @param {string=} file - the file name for which state is desired
|
||||
* @return {Object} - a file state object (as documented within
|
||||
* intializeFileState above), or a set of file state objects if
|
||||
* file is omitted.
|
||||
* An array of library names that contain JavaScript builtins definitions.
|
||||
*
|
||||
* @returns {Array} - array of library names.
|
||||
*/
|
||||
function getFileState(dir, file) {
|
||||
initFileState(dir, file);
|
||||
|
||||
if (file === undefined) {
|
||||
return fileState[dir];
|
||||
} else {
|
||||
return fileState[dir][file];
|
||||
}
|
||||
function getBuiltins() {
|
||||
return builtinLibraryNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a new outer scope object from the parser worker, if necessary
|
||||
*
|
||||
* @param {string} dir - the directory name for which the outer scope is to
|
||||
* be refreshed
|
||||
* @param {string} file - the file name for which the outer scope is to be
|
||||
* refreshed
|
||||
* @param {string} text - the text of the file for which the outer scope is
|
||||
* to be refreshed
|
||||
* Read in the json files that have type information for the builtins, dom,etc
|
||||
*/
|
||||
function refreshOuterScope(dir, file, text) {
|
||||
function initTernEnv() {
|
||||
var path = module.uri.substring(0, module.uri.lastIndexOf("/") + 1) + "thirdparty/tern/defs/",
|
||||
files = builtinFiles,
|
||||
library;
|
||||
|
||||
if (text.length > MAX_TEXT_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
var state = getFileState(dir, file);
|
||||
|
||||
// if there is not yet an outer scope or if the file has changed then
|
||||
// we might need to update the outer scope
|
||||
if (state.scope === null || state.dirtyFile) {
|
||||
if (!state.active) {
|
||||
var path = dir + file,
|
||||
entry = new NativeFileSystem.FileEntry(path);
|
||||
|
||||
// the outer scope worker is about to be active
|
||||
state.active = true;
|
||||
|
||||
// the file will be clean since the last outer scope request
|
||||
state.dirtyFile = false;
|
||||
|
||||
// send text to the parser worker
|
||||
outerScopeWorker.postMessage({
|
||||
type : HintUtils.SCOPE_MSG_TYPE,
|
||||
dir : dir,
|
||||
file : file,
|
||||
text : text,
|
||||
force : !state.scope
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inner scope information for a given file and offset if a suitable
|
||||
* global scope object is availble; otherwise, return a promise for such
|
||||
* information, resolved when a suitable global scope object becomes
|
||||
* available.
|
||||
*
|
||||
* @param {string} dir - the directory name for which the inner scope is to
|
||||
* be refreshed
|
||||
* @param {string} file - the file name for which the inner scope is to be
|
||||
* refreshed
|
||||
* @param {number} offset - offset into the text at which the inner scope
|
||||
* is to be refreshed
|
||||
* @return {Object + jQuery.Promise} - inner scope information, or a promise
|
||||
* for such information, including the local scope object and lists of
|
||||
* identifiers, properties, globals, literals and associations.
|
||||
*/
|
||||
function refreshInnerScope(dir, file, offset) {
|
||||
|
||||
/*
|
||||
* Filter a list of tokens using a given scope object
|
||||
*
|
||||
* @param {Array.<Object>} tokens - a list of identifier tokens
|
||||
* @param {Scope} scope - a scope object
|
||||
* @return {Array.<Object>} - the sublist of the input list that
|
||||
* contains all and only the identifier tokens in scope
|
||||
* w.r.t. to the given scope
|
||||
*/
|
||||
function filterByScope(tokens, scope) {
|
||||
return tokens.filter(function (id) {
|
||||
var level = scope.contains(id.value);
|
||||
return (level >= 0);
|
||||
files.forEach(function (i) {
|
||||
DocumentManager.getDocumentForPath(path + i).done(function (document) {
|
||||
library = JSON.parse(document.getText());
|
||||
builtinLibraryNames.push(library["!name"]);
|
||||
ternEnvironment.push(library);
|
||||
}).fail(function (error) {
|
||||
console.log("failed to read tern config file " + i);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initTernEnv();
|
||||
|
||||
/**
|
||||
* Send a message to the tern worker - if the worker is being initialized,
|
||||
* the message will not be posted until initialization is complete
|
||||
*/
|
||||
function postMessage(msg) {
|
||||
ternPromise.done(function (ternWorker) {
|
||||
ternWorker.postMessage(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a Promise for the definition from TernJS, for the file & offset passed in.
|
||||
* @return {jQuery.Promise} - a promise that will resolve to definition when
|
||||
* it is done
|
||||
*/
|
||||
function getJumptoDef(dir, file, offset, text) {
|
||||
postMessage({
|
||||
type: HintUtils.TERN_JUMPTODEF_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
offset: offset,
|
||||
text: text
|
||||
});
|
||||
|
||||
var $deferredJump = $.Deferred();
|
||||
pendingTernRequests[file] = $deferredJump;
|
||||
return $deferredJump.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request Jump-To-Definition from Tern.
|
||||
*
|
||||
* @param {session} session - the session
|
||||
* @param {Document} document - the document
|
||||
* @param {number} offset - the offset into the document
|
||||
* @return {jQuery.Promise} - The promise will not complete until tern
|
||||
* has completed.
|
||||
*/
|
||||
function requestJumptoDef(session, document, offset) {
|
||||
var path = document.file.fullPath,
|
||||
split = HintUtils.splitPath(path),
|
||||
dir = split.dir,
|
||||
file = split.file;
|
||||
|
||||
/*
|
||||
* Combine a particular property from a set of sets using a given add
|
||||
* operation
|
||||
*
|
||||
* @param {Object} sets - a set of sets
|
||||
* @param {string} propName - the property to pick out from each set
|
||||
* @param {Function} add - the function that combines properties from
|
||||
* each set
|
||||
* @return {Object}- the result of combining each set's property using
|
||||
* the add function
|
||||
*/
|
||||
function merge(sets, propName, add) {
|
||||
var combinedSet = {},
|
||||
nextSet,
|
||||
file;
|
||||
|
||||
for (file in sets) {
|
||||
if (sets.hasOwnProperty(file)) {
|
||||
nextSet = sets[file][propName];
|
||||
if (nextSet) {
|
||||
add(combinedSet, nextSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return combinedSet;
|
||||
}
|
||||
|
||||
/*
|
||||
* Combine properties from files in the current file's directory into
|
||||
* a single list.
|
||||
*
|
||||
* @param {string} dir - the directory name of the files for which
|
||||
* property lists should be merged
|
||||
* @param {Array.<Object>} - the combined list of property tokens
|
||||
*/
|
||||
function mergeProperties(dir) {
|
||||
|
||||
function addPropObjs(obj1, obj2) {
|
||||
function addToObj(obj, token) {
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, token.value)) {
|
||||
obj[token.value] = token;
|
||||
}
|
||||
}
|
||||
|
||||
obj2.forEach(function (token) {
|
||||
addToObj(obj1, token);
|
||||
});
|
||||
}
|
||||
|
||||
var stateMap = getFileState(dir),
|
||||
propObj = merge(stateMap, "properties", addPropObjs),
|
||||
propList = [],
|
||||
propName;
|
||||
|
||||
for (propName in propObj) {
|
||||
if (Object.prototype.hasOwnProperty.call(propObj, propName)) {
|
||||
propList.push(propObj[propName]);
|
||||
}
|
||||
}
|
||||
|
||||
return propList;
|
||||
}
|
||||
|
||||
/*
|
||||
* Combine association set objects from all of the files in a given
|
||||
* directory
|
||||
*
|
||||
* @param {string} dir - the directory name of the files for which
|
||||
* association sets should be merged
|
||||
* @param {Object} - the combined association set object
|
||||
*/
|
||||
function mergeAssociations(dir) {
|
||||
function addAssocSets(list1, list2) {
|
||||
var name;
|
||||
|
||||
function addAssocObjs(assoc1, assoc2) {
|
||||
var name;
|
||||
|
||||
for (name in assoc2) {
|
||||
if (Object.prototype.hasOwnProperty.call(assoc2, name)) {
|
||||
if (Object.prototype.hasOwnProperty.call(assoc1, name)) {
|
||||
assoc1[name] = assoc1[name] + assoc2[name];
|
||||
} else {
|
||||
assoc1[name] = assoc2[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (name in list2) {
|
||||
if (Object.prototype.hasOwnProperty.call(list2, name)) {
|
||||
if (Object.prototype.hasOwnProperty.call(list1, name)) {
|
||||
addAssocObjs(list1[name], list2[name]);
|
||||
} else {
|
||||
list1[name] = list2[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stateMap = getFileState(dir);
|
||||
|
||||
return merge(stateMap, "associations", addAssocSets);
|
||||
}
|
||||
|
||||
var state = getFileState(dir, file);
|
||||
var ternPromise = getJumptoDef(dir, file, offset, document.getText());
|
||||
|
||||
// If there is no outer scope, the inner scope request is deferred.
|
||||
if (!state.scope) {
|
||||
if (pendingRequest && pendingRequest.deferred.state() === "pending") {
|
||||
pendingRequest.deferred.reject();
|
||||
}
|
||||
return {promise: ternPromise};
|
||||
}
|
||||
|
||||
pendingRequest = {
|
||||
dir : dir,
|
||||
file : file,
|
||||
offset : offset,
|
||||
deferred : $.Deferred()
|
||||
};
|
||||
|
||||
// Request the outer scope from the parser worker.
|
||||
DocumentManager.getDocumentForPath(dir + file).done(function (document) {
|
||||
refreshOuterScope(dir, file, document.getText());
|
||||
});
|
||||
return { promise: pendingRequest.deferred.promise() };
|
||||
/**
|
||||
* Handle the response from the tern web worker when
|
||||
* it responds with the definition
|
||||
*
|
||||
* @param response - the response from the worker
|
||||
*/
|
||||
function handleJumptoDef(response) {
|
||||
|
||||
var file = response.file;
|
||||
var $deferredJump = pendingTernRequests[file];
|
||||
|
||||
pendingTernRequests[file] = null;
|
||||
|
||||
if ($deferredJump) {
|
||||
$deferredJump.resolveWith(null, [response]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pending request waiting for the tern-worker to complete.
|
||||
*
|
||||
* @param {string} file - the name of the file
|
||||
* @param {string} type - the type of request
|
||||
* @param {jQuery.Deferred} deferredRequest - the $.Deferred object to save
|
||||
*/
|
||||
function addPendingRequest(file, offset, type) {
|
||||
var requests,
|
||||
key = file + "@" + offset,
|
||||
$deferredRequest;
|
||||
if (Object.prototype.hasOwnProperty.call(pendingTernRequests, key)) {
|
||||
requests = pendingTernRequests[key];
|
||||
} else {
|
||||
// The inner scope will be clean after this
|
||||
state.dirtyScope = false;
|
||||
|
||||
// Try to find an inner scope from the current outer scope
|
||||
var innerScope = state.scope.findChild(offset);
|
||||
|
||||
if (!innerScope) {
|
||||
// we may have failed to find a child scope because a
|
||||
// character was added to the end of the file, outside of
|
||||
// the (now out-of-date and currently-being-updated)
|
||||
// outer scope. Hence, if offset is greater than the range
|
||||
// of the outerScope, we manually set innerScope to the
|
||||
// outerScope
|
||||
innerScope = state.scope;
|
||||
}
|
||||
|
||||
// FIXME: This could be more efficient if instead of filtering
|
||||
// the entire list of identifiers we just used the identifiers
|
||||
// in the scope of innerScope, but that list doesn't have the
|
||||
// accumulated position information.
|
||||
var identifiersForScope = filterByScope(state.identifiers, innerScope),
|
||||
propertiesForFile = mergeProperties(dir),
|
||||
associationsForFile = mergeAssociations(dir);
|
||||
|
||||
return {
|
||||
scope : innerScope,
|
||||
identifiers : identifiersForScope,
|
||||
globals : state.globals,
|
||||
literals : state.literals,
|
||||
properties : propertiesForFile,
|
||||
associations: associationsForFile
|
||||
};
|
||||
requests = {};
|
||||
pendingTernRequests[key] = requests;
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(requests, type)) {
|
||||
$deferredRequest = requests[type];
|
||||
} else {
|
||||
requests[type] = $deferredRequest = $.Deferred();
|
||||
}
|
||||
return $deferredRequest.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new inner scope and related info, if possible. If there is no
|
||||
* outer scope for the given file, a promise will be returned instead.
|
||||
* (See refreshInnerScope above.)
|
||||
* Get a Promise for the completions from TernJS, for the file & offset passed in.
|
||||
* @return {jQuery.Promise} - a promise that will resolve to an array of completions when
|
||||
* it is done
|
||||
*/
|
||||
function getTernHints(dir, file, offset, text) {
|
||||
postMessage({
|
||||
type: HintUtils.TERN_COMPLETIONS_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
offset: offset,
|
||||
text: text
|
||||
});
|
||||
|
||||
return addPendingRequest(file, offset, HintUtils.TERN_COMPLETIONS_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Promise for all of the known properties from TernJS, for the directory and file.
|
||||
* The properties will be used as guesses in tern.
|
||||
*
|
||||
* @return {jQuery.Promise} - a promise that will resolve to an array of properties when
|
||||
* it is done
|
||||
*/
|
||||
function getTernProperties(dir, file, offset, text) {
|
||||
postMessage({
|
||||
type: HintUtils.TERN_GET_PROPERTIES_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
offset: offset,
|
||||
text: text
|
||||
});
|
||||
|
||||
return addPendingRequest(file, offset, HintUtils.TERN_GET_PROPERTIES_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Promise for the function type from TernJS.
|
||||
* @return {jQuery.Promise} - a promise that will resolve to the function type of the function being called.
|
||||
*/
|
||||
function getTernFunctionType(dir, file, pos, offset, text) {
|
||||
postMessage({
|
||||
type: HintUtils.TERN_CALLED_FUNC_TYPE_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
pos: pos,
|
||||
offset: offset,
|
||||
text: text
|
||||
});
|
||||
|
||||
return addPendingRequest(file, offset, HintUtils.TERN_CALLED_FUNC_TYPE_MSG);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Request hints from Tern.
|
||||
*
|
||||
* Note that successive calls to getScope may return the same objects, so
|
||||
* clients that wish to modify those objects (e.g., by annotating them based
|
||||
@ -372,49 +260,146 @@ define(function (require, exports, module) {
|
||||
* desired
|
||||
* @param {number} offset - the offset into the document at which scope
|
||||
* info is desired
|
||||
* @return {Object + jQuery.Promise} - the inner scope info, or a promise
|
||||
* for such info. (See refreshInnerScope above.)
|
||||
* @return {jQuery.Promise} - The promise will not complete until the tern
|
||||
* hints have completed.
|
||||
*/
|
||||
function getScopeInfo(document, offset) {
|
||||
function requestHints(session, document, offset) {
|
||||
var path = document.file.fullPath,
|
||||
split = HintUtils.splitPath(path),
|
||||
dir = split.dir,
|
||||
file = split.file;
|
||||
|
||||
return refreshInnerScope(dir, file, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the inner scope dirty? (It is if the outer scope has changed since
|
||||
* the last inner scope request)
|
||||
*
|
||||
* @param {Document} document - the document for which the last requested
|
||||
* inner scope may or may not be dirty
|
||||
* @return {boolean} - is the inner scope dirty?
|
||||
*/
|
||||
function isScopeDirty(document) {
|
||||
var path = document.file.fullPath,
|
||||
split = HintUtils.splitPath(path),
|
||||
dir = split.dir,
|
||||
file = split.file,
|
||||
state = getFileState(dir, file);
|
||||
var $deferredHints = $.Deferred(),
|
||||
hintPromise,
|
||||
fnTypePromise,
|
||||
propsPromise;
|
||||
|
||||
return state.dirtyScope;
|
||||
hintPromise = getTernHints(dir, file, offset, document.getText());
|
||||
var sessionType = session.getType();
|
||||
if (sessionType.property) {
|
||||
propsPromise = getTernProperties(dir, file, offset, document.getText());
|
||||
} else {
|
||||
var $propsDeferred = $.Deferred();
|
||||
propsPromise = $propsDeferred.promise();
|
||||
$propsDeferred.resolveWith(null);
|
||||
}
|
||||
|
||||
if (sessionType.showFunctionType) {
|
||||
// Show function sig
|
||||
fnTypePromise = getTernFunctionType(dir, file, sessionType.functionCallPos, offset, document.getText());
|
||||
} else {
|
||||
var $fnTypeDeferred = $.Deferred();
|
||||
fnTypePromise = $fnTypeDeferred.promise();
|
||||
$fnTypeDeferred.resolveWith(null);
|
||||
}
|
||||
$.when(hintPromise, fnTypePromise, propsPromise).done(
|
||||
function (completions, fnType, properties) {
|
||||
session.setTernHints(completions);
|
||||
session.setFnType(fnType);
|
||||
session.setTernProperties(properties);
|
||||
|
||||
$deferredHints.resolveWith(null);
|
||||
}
|
||||
);
|
||||
return {promise: $deferredHints.promise()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a file as dirty, which may cause a later outer scope request to
|
||||
* trigger a reparse request.
|
||||
*
|
||||
* @param {string} dir - the directory name of the file to be marked dirty
|
||||
* @param {string} file - the file name of the file to be marked dirty
|
||||
* Get any pending $.Deferred object waiting on the specified file and request type
|
||||
* @param {string} file - the file
|
||||
* @param {string} type - the type of request
|
||||
* @param {jQuery.Deferred} - the $.Deferred for the request
|
||||
*/
|
||||
function markFileDirty(dir, file) {
|
||||
var state = getFileState(dir, file);
|
||||
function getPendingRequest(file, offset, type) {
|
||||
var key = file + "@" + offset;
|
||||
if (CollectionUtils.hasProperty(pendingTernRequests, key)) {
|
||||
var requests = pendingTernRequests[key],
|
||||
requestType = requests[type];
|
||||
|
||||
state.dirtyFile = true;
|
||||
delete pendingTernRequests[key][type];
|
||||
|
||||
if (!Object.keys(requests).length) {
|
||||
delete pendingTernRequests[key];
|
||||
}
|
||||
|
||||
return requestType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the response from the tern web worker when
|
||||
* it responds with the list of completions
|
||||
*
|
||||
* @param {{dir:string, file:string, offset:number, completions:Array.<string>}} response - the response from the worker
|
||||
*/
|
||||
function handleTernCompletions(response) {
|
||||
|
||||
var file = response.file,
|
||||
offset = response.offset,
|
||||
completions = response.completions,
|
||||
properties = response.properties,
|
||||
fnType = response.fnType,
|
||||
type = response.type,
|
||||
$deferredHints = getPendingRequest(file, offset, type);
|
||||
|
||||
if ($deferredHints) {
|
||||
if (completions) {
|
||||
$deferredHints.resolveWith(null, [completions]);
|
||||
} else if (properties) {
|
||||
$deferredHints.resolveWith(null, [properties]);
|
||||
} else if (fnType) {
|
||||
$deferredHints.resolveWith(null, [fnType]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file a relative path
|
||||
* @return {string} returns the path we resolved when we tried to parse the file, or undefined
|
||||
*/
|
||||
function getResolvedPath(file) {
|
||||
return resolvedFiles[file];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request from the worker for text of a file
|
||||
*
|
||||
* @param {{file:string}} request - the request from the worker. Should be an Object containing the name
|
||||
* of the file tern wants the contents of
|
||||
*/
|
||||
function handleTernGetFile(request) {
|
||||
|
||||
function replyWith(name, txt) {
|
||||
postMessage({
|
||||
type: HintUtils.TERN_GET_FILE_MSG,
|
||||
file: name,
|
||||
text: txt
|
||||
});
|
||||
}
|
||||
|
||||
var name = request.file;
|
||||
DocumentManager.getDocumentForPath(rootTernDir + name).done(function (document) {
|
||||
resolvedFiles[name] = rootTernDir + name;
|
||||
replyWith(name, document.getText());
|
||||
})
|
||||
.fail(function () {
|
||||
if (projectRoot) {
|
||||
// Try relative to project root
|
||||
DocumentManager.getDocumentForPath(projectRoot + name).done(function (document) {
|
||||
resolvedFiles[name] = projectRoot + name;
|
||||
replyWith(name, document.getText());
|
||||
})
|
||||
.fail(function () {
|
||||
replyWith(name, "");
|
||||
});
|
||||
} else {
|
||||
// Need to send something back to tern - it will wait
|
||||
// until all the files have been retrieved before doing its calculations
|
||||
replyWith(name, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called each time a new editor becomes active. Refreshes the outer scopes
|
||||
* of the given file as well as of the other files in the given directory.
|
||||
@ -425,13 +410,18 @@ define(function (require, exports, module) {
|
||||
var path = document.file.fullPath,
|
||||
split = HintUtils.splitPath(path),
|
||||
dir = split.dir,
|
||||
files = [],
|
||||
file = split.file;
|
||||
|
||||
var ternDeferred = $.Deferred();
|
||||
ternPromise = ternDeferred.promise();
|
||||
pendingTernRequests = [];
|
||||
resolvedFiles = {};
|
||||
projectRoot = ProjectManager.getProjectRoot() ? ProjectManager.getProjectRoot().fullPath : null;
|
||||
|
||||
NativeFileSystem.resolveNativeFileSystemPath(dir, function (dirEntry) {
|
||||
var reader = dirEntry.createReader();
|
||||
|
||||
markFileDirty(dir, file);
|
||||
|
||||
reader.readEntries(function (entries) {
|
||||
entries.slice(0, MAX_FILES_IN_DIR).forEach(function (entry) {
|
||||
if (entry.isFile) {
|
||||
@ -443,135 +433,55 @@ define(function (require, exports, module) {
|
||||
if (file.indexOf(".") > 1) { // ignore /.dotfiles
|
||||
var languageID = LanguageManager.getLanguageForPath(entry.fullPath).getId();
|
||||
if (languageID === HintUtils.LANGUAGE_ID) {
|
||||
DocumentManager.getDocumentForPath(path).done(function (document) {
|
||||
refreshOuterScope(dir, file, document.getText());
|
||||
});
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
initTernServer(dir, files);
|
||||
ternDeferred.resolveWith(null, [_ternWorker]);
|
||||
}, function (err) {
|
||||
console.log("Unable to refresh directory: " + err);
|
||||
refreshOuterScope(dir, file, document.getText());
|
||||
});
|
||||
}, function (err) {
|
||||
console.log("Directory \"%s\" does not exist", dir);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Called each time the file associated with the active edtor changes.
|
||||
* Called each time the file associated with the active editor changes.
|
||||
* Marks the file as being dirty and refresh its outer scope.
|
||||
*
|
||||
* @param {Document} document - the document that has changed
|
||||
*/
|
||||
function handleFileChange(document) {
|
||||
var path = document.file.fullPath,
|
||||
split = HintUtils.splitPath(path),
|
||||
dir = split.dir,
|
||||
file = split.file;
|
||||
|
||||
markFileDirty(dir, file);
|
||||
refreshOuterScope(dir, file, document.getText());
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive an outer scope object from the parser worker and resolves any
|
||||
* deferred inner scope requests.
|
||||
*
|
||||
* @param {Object} response - the parser response object, which includes
|
||||
* the global scope and lists of identifiers, properties, globals,
|
||||
* literals and associations.
|
||||
*/
|
||||
function handleOuterScope(response) {
|
||||
var dir = response.dir,
|
||||
file = response.file,
|
||||
path = dir + file,
|
||||
state = getFileState(dir, file),
|
||||
scopeInfo;
|
||||
|
||||
if (state.active) {
|
||||
state.active = false;
|
||||
if (response.success) {
|
||||
state.scope = Scope.rebuild(response.scope);
|
||||
|
||||
// The outer scope should cover the entire file
|
||||
state.scope.range.start = 0;
|
||||
state.scope.range.end = response.length;
|
||||
|
||||
state.globals = response.globals;
|
||||
state.identifiers = response.identifiers;
|
||||
state.properties = response.properties;
|
||||
state.literals = response.literals;
|
||||
state.associations = response.associations;
|
||||
|
||||
state.dirtyScope = true;
|
||||
|
||||
if (state.dirtyFile) {
|
||||
DocumentManager.getDocumentForPath(path).done(function (document) {
|
||||
refreshOuterScope(dir, file, document.getText());
|
||||
});
|
||||
}
|
||||
|
||||
if (pendingRequest !== null && pendingRequest.dir === dir &&
|
||||
pendingRequest.file === file) {
|
||||
if (pendingRequest.deferred.state() === "pending") {
|
||||
scopeInfo = refreshInnerScope(dir, file, pendingRequest.offset);
|
||||
if (scopeInfo && !scopeInfo.deferred) {
|
||||
pendingRequest.deferred.resolveWith(null, [scopeInfo]);
|
||||
pendingRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("Expired scope request: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
// handle response objects, otherwise log the message
|
||||
outerScopeWorker.addEventListener("message", function (e) {
|
||||
_ternWorker.addEventListener("message", function (e) {
|
||||
var response = e.data,
|
||||
type = response.type;
|
||||
|
||||
if (type === HintUtils.SCOPE_MSG_TYPE) {
|
||||
handleOuterScope(response);
|
||||
if (type === HintUtils.TERN_COMPLETIONS_MSG ||
|
||||
type === HintUtils.TERN_CALLED_FUNC_TYPE_MSG ||
|
||||
type === HintUtils.TERN_GET_PROPERTIES_MSG) {
|
||||
// handle any completions the worker calculated
|
||||
handleTernCompletions(response);
|
||||
} else if (type === HintUtils.TERN_GET_FILE_MSG) {
|
||||
// handle a request for the contents of a file
|
||||
handleTernGetFile(response);
|
||||
} else if (type === HintUtils.TERN_JUMPTODEF_MSG) {
|
||||
handleJumptoDef(response);
|
||||
} else {
|
||||
console.log("Worker: " + (response.log || response));
|
||||
}
|
||||
});
|
||||
|
||||
// reset state on project change
|
||||
$(ProjectManager)
|
||||
.on(HintUtils.eventName("beforeProjectClose"),
|
||||
function (event, projectRoot) {
|
||||
fileState = {};
|
||||
});
|
||||
|
||||
// relocate scope information on file rename
|
||||
$(DocumentManager)
|
||||
.on(HintUtils.eventName("fileNameChange"),
|
||||
function (event, oldName, newName) {
|
||||
var oldSplit = HintUtils.splitPath(oldName),
|
||||
oldDir = oldSplit.dir,
|
||||
oldFile = oldSplit.file,
|
||||
newSplit = HintUtils.splitPath(newName),
|
||||
newDir = newSplit.dir,
|
||||
newFile = newSplit.file;
|
||||
|
||||
if (fileState.hasOwnProperty(oldDir) &&
|
||||
fileState[oldDir].hasOwnProperty(oldFile)) {
|
||||
if (!fileState.hasOwnProperty(newDir)) {
|
||||
fileState[newDir] = {};
|
||||
}
|
||||
fileState[newDir][newFile] = fileState[oldDir][oldFile];
|
||||
delete fileState[oldDir][oldFile];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
exports.getBuiltins = getBuiltins;
|
||||
exports.handleEditorChange = handleEditorChange;
|
||||
exports.handleFileChange = handleFileChange;
|
||||
exports.getScopeInfo = getScopeInfo;
|
||||
exports.isScopeDirty = isScopeDirty;
|
||||
exports.requestJumptoDef = requestJumptoDef;
|
||||
exports.requestHints = requestHints;
|
||||
exports.getTernHints = getTernHints;
|
||||
exports.getResolvedPath = getResolvedPath;
|
||||
});
|
||||
|
@ -27,7 +27,8 @@
|
||||
define(function (require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var HintUtils = require("HintUtils"),
|
||||
var StringMatch = brackets.getModule("utils/StringMatch"),
|
||||
HintUtils = require("HintUtils"),
|
||||
ScopeManager = require("ScopeManager");
|
||||
|
||||
/**
|
||||
@ -40,24 +41,13 @@ define(function (require, exports, module) {
|
||||
function Session(editor) {
|
||||
this.editor = editor;
|
||||
this.path = editor.document.file.fullPath;
|
||||
this.ternHints = [];
|
||||
this.ternProperties = [];
|
||||
this.fnType = null;
|
||||
this.builtins = ScopeManager.getBuiltins();
|
||||
this.builtins.push("requirejs.js"); // consider these globals as well.
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the scope information assocated with the current session
|
||||
*
|
||||
* @param {Object} scopeInfo - scope information, including the scope and
|
||||
* lists of identifiers, globals, literals and properties, and a set
|
||||
* of associations
|
||||
*/
|
||||
Session.prototype.setScopeInfo = function (scopeInfo) {
|
||||
this.scope = scopeInfo.scope;
|
||||
this.identifiers = scopeInfo.identifiers;
|
||||
this.globals = scopeInfo.globals;
|
||||
this.literals = scopeInfo.literals;
|
||||
this.properties = scopeInfo.properties;
|
||||
this.associations = scopeInfo.associations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the name of the file associated with the current session
|
||||
*
|
||||
@ -76,6 +66,17 @@ define(function (require, exports, module) {
|
||||
Session.prototype.getCursor = function () {
|
||||
return this.editor.getCursorPos();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text of a line.
|
||||
*
|
||||
* @param {number} line - the line number
|
||||
* @return {string} - the text of the line
|
||||
*/
|
||||
Session.prototype.getLine = function (line) {
|
||||
var doc = this.editor.document;
|
||||
return doc.getLine(line);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the offset of the current cursor position
|
||||
@ -172,14 +173,16 @@ define(function (require, exports, module) {
|
||||
query = "";
|
||||
|
||||
if (token) {
|
||||
if (token.string !== ".") {
|
||||
// If the token string is not an identifier, then the query string
|
||||
// is empty.
|
||||
if (HintUtils.maybeIdentifier(token.string)) {
|
||||
query = token.string.substring(0, token.string.length - (token.end - cursor.ch));
|
||||
query = query.trim();
|
||||
}
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find the context of a property lookup. For example, for a lookup
|
||||
* foo(bar, baz(quux)).prop, foo is the context.
|
||||
@ -217,20 +220,52 @@ define(function (require, exports, module) {
|
||||
* Get the type of the current session, i.e., whether it is a property
|
||||
* lookup and, if so, what the context of the lookup is.
|
||||
*
|
||||
* @return {{property: boolean, context: string}} - a pair consisting
|
||||
* @return {{property: boolean,
|
||||
showFunctionType:boolean,
|
||||
context: string,
|
||||
functionCallPos: {line:number, ch:number}}} - an Object consisting
|
||||
* of a {boolean} "property" that indicates whether or not the type of
|
||||
* the session is a property lookup, and a {string} "context" that
|
||||
* indicates the object context (as described in getContext above) of
|
||||
* the property lookup, or null if there is none. The context is
|
||||
* always null for non-property lookups.
|
||||
* a {boolean} "showFunctionType" indicating if the function type should
|
||||
* be displayed instead of normal hints. If "showFunctionType" is true, then
|
||||
* then "functionCallPos" will be an object with line & col information of the
|
||||
* function being called
|
||||
*/
|
||||
Session.prototype.getType = function () {
|
||||
var propertyLookup = false,
|
||||
context = null,
|
||||
cursor = this.getCursor(),
|
||||
token = this.getToken(cursor);
|
||||
var propertyLookup = false,
|
||||
inFunctionCall = false,
|
||||
showFunctionType = false,
|
||||
context = null,
|
||||
cursor = this.getCursor(),
|
||||
functionCallPos,
|
||||
token = this.getToken(cursor);
|
||||
|
||||
if (token) {
|
||||
if (token.state.lexical.info === "call") {
|
||||
inFunctionCall = true;
|
||||
if (this.getQuery().length > 0) {
|
||||
inFunctionCall = false;
|
||||
showFunctionType = false;
|
||||
} else {
|
||||
showFunctionType = true;
|
||||
var col = token.state.lexical.column,
|
||||
line,
|
||||
e,
|
||||
found;
|
||||
for (line = this.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
|
||||
if (this.getLine(line).charAt(col) === "(") {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
functionCallPos = {line: line, ch: col};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (token.string === ".") {
|
||||
propertyLookup = true;
|
||||
context = this.getContext(cursor);
|
||||
@ -245,10 +280,14 @@ define(function (require, exports, module) {
|
||||
context = this.getContext(cursor);
|
||||
}
|
||||
}
|
||||
if (propertyLookup) { showFunctionType = false; }
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
property: propertyLookup,
|
||||
inFunctionCall: inFunctionCall,
|
||||
showFunctionType: showFunctionType,
|
||||
functionCallPos: functionCallPos,
|
||||
context: context
|
||||
};
|
||||
};
|
||||
@ -256,309 +295,125 @@ define(function (require, exports, module) {
|
||||
/**
|
||||
* Get a list of hints for the current session using the current scope
|
||||
* information.
|
||||
*
|
||||
*
|
||||
* @param {string} query - the query prefix
|
||||
* @param {StringMatcher} matcher - the class to find query matches and sort the results
|
||||
* @return {Array.<Object>} - the sorted list of hints for the current
|
||||
* session.
|
||||
*/
|
||||
Session.prototype.getHints = function () {
|
||||
|
||||
/*
|
||||
* Comparator for sorting tokens according to minimum distance from
|
||||
* a given position
|
||||
*
|
||||
* @param {number} pos - the position to which a token's occurrences
|
||||
* are compared
|
||||
* @param {Function} - the comparator function
|
||||
*/
|
||||
function compareByPosition(pos) {
|
||||
|
||||
/*
|
||||
* Compute the minimum distance between a token, with which is
|
||||
* associated a sorted list of positions, and a given offset.
|
||||
*
|
||||
* @param {number} pos - the position to which a token's occurrences
|
||||
* are compared.
|
||||
* @param {Object} token - a hint token, annotated with a list of
|
||||
* occurrence positions
|
||||
* @return number - the least distance of an occurrence of token to
|
||||
* pos, or Infinity if there are no occurrences
|
||||
*/
|
||||
function minDistToPos(pos, token) {
|
||||
var arr = token.positions,
|
||||
low = 0,
|
||||
high = arr.length,
|
||||
middle = Math.floor(high / 2),
|
||||
dist;
|
||||
Session.prototype.getHints = function (query, matcher) {
|
||||
|
||||
if (high === 0) {
|
||||
return Infinity;
|
||||
} else {
|
||||
// binary search for the position
|
||||
while (low < middle && middle < high) {
|
||||
if (arr[middle] < pos) {
|
||||
low = middle;
|
||||
middle += Math.floor((high - middle) / 2);
|
||||
} else if (arr[middle] > pos) {
|
||||
high = middle;
|
||||
middle = low + Math.floor((middle - low) / 2);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// the closest position is off by no more than one
|
||||
dist = Math.abs(arr[middle] - pos);
|
||||
if (middle > 0) {
|
||||
dist = Math.min(dist, Math.abs(arr[middle - 1] - pos));
|
||||
}
|
||||
if (middle + 1 < arr.length) {
|
||||
dist = Math.min(dist, Math.abs(arr[middle + 1] - pos));
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
|
||||
return function (a, b) {
|
||||
|
||||
/*
|
||||
* Look up the cached minimum distance from an occurrence of
|
||||
* the token to the given position, calculating and storing it
|
||||
* if needed.
|
||||
*
|
||||
* @param {Object} token - the token from which the minimum
|
||||
* distance to position pos is required
|
||||
* @return {number} - the least distance of an occurrence of
|
||||
* token to position pos
|
||||
*/
|
||||
function getDistToPos(token) {
|
||||
var dist;
|
||||
|
||||
if (token.distToPos >= 0) {
|
||||
dist = token.distToPos;
|
||||
} else {
|
||||
dist = minDistToPos(pos, token);
|
||||
token.distToPos = dist;
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
var aDist = getDistToPos(a),
|
||||
bDist = getDistToPos(b);
|
||||
|
||||
if (aDist === Infinity) {
|
||||
if (bDist === Infinity) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (bDist === Infinity) {
|
||||
return -1;
|
||||
} else {
|
||||
return aDist - bDist;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (query === undefined) {
|
||||
query = "";
|
||||
}
|
||||
|
||||
/*
|
||||
* Comparator for sorting tokens lexicographically according to scope,
|
||||
* assuming the scope level has already been annotated.
|
||||
*
|
||||
* @param {Object} a - a token
|
||||
* @param {Object} b - another token
|
||||
* @param {number} - comparator value that indicates whether a is more
|
||||
* tightly scoped than b
|
||||
*/
|
||||
function compareByScope(a, b) {
|
||||
var adepth = a.level;
|
||||
var bdepth = b.level;
|
||||
|
||||
if (adepth >= 0) {
|
||||
if (bdepth >= 0) {
|
||||
return adepth - bdepth;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (bdepth >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Comparator for sorting tokens by name
|
||||
*
|
||||
* @param {Object} a - a token
|
||||
* @param {Object} b - another token
|
||||
* @return {number} - comparator value that indicates whether the name
|
||||
* of token a is lexicographically lower than the name of token b
|
||||
*/
|
||||
function compareByName(a, b) {
|
||||
if (a.value === b.value) {
|
||||
return 0;
|
||||
} else if (a.value < b.value) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Comparator for sorting tokens by path, such that a <= b if
|
||||
* a.path === path
|
||||
*
|
||||
* @param {string} path - the target path name
|
||||
* @return {Function} - the comparator function
|
||||
*/
|
||||
function compareByPath(path) {
|
||||
return function (a, b) {
|
||||
if (a.path === path) {
|
||||
if (b.path === path) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (b.path === path) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Comparator for sorting properties w.r.t. an association object.
|
||||
*
|
||||
* @param {Object} assoc - an association object
|
||||
* @return {Function} - the comparator function
|
||||
*/
|
||||
function compareByAssociation(assoc) {
|
||||
return function (a, b) {
|
||||
if (Object.prototype.hasOwnProperty.call(assoc, a.value)) {
|
||||
if (Object.prototype.hasOwnProperty.call(assoc, b.value)) {
|
||||
return assoc[a.value] - assoc[b.value];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (Object.prototype.hasOwnProperty.call(assoc, b.value)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Forms the lexicographical composition of comparators, i.e.,
|
||||
* "a lex(c1,c2) b" iff "a c1 b or (a = b and a c2 b)"
|
||||
*
|
||||
* @param {Function} compare1 - a comparator
|
||||
* @param {Function} compare2 - another comparator
|
||||
* @return {Function} - the lexicographic composition of comparator1
|
||||
* and comparator2
|
||||
*/
|
||||
function lexicographic(compare1, compare2) {
|
||||
return function (a, b) {
|
||||
var result = compare1(a, b);
|
||||
if (result === 0) {
|
||||
return compare2(a, b);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* A comparator for identifiers: the lexicographic combination of
|
||||
* scope, position and name.
|
||||
*
|
||||
* @param {number} pos - the target position by which identifiers are
|
||||
* compared
|
||||
* @return {Function} - the comparator function
|
||||
*/
|
||||
function compareIdentifiers(pos) {
|
||||
return lexicographic(compareByScope,
|
||||
lexicographic(compareByPosition(pos),
|
||||
compareByName));
|
||||
}
|
||||
|
||||
/*
|
||||
* A comparator for properties: the lexicographic combination of
|
||||
* association, path name, position, and name.
|
||||
*
|
||||
* @param {Object} assoc - the association by which properties are
|
||||
* compared
|
||||
* @param {string} path - the path name by which properties are
|
||||
* compared
|
||||
* @param {number} pos - the target position by which properties are
|
||||
* compared
|
||||
* @return {Function} - the comparator function
|
||||
*/
|
||||
function compareProperties(assoc, path, pos) {
|
||||
return lexicographic(compareByAssociation(assoc),
|
||||
lexicographic(compareByPath(path),
|
||||
lexicographic(compareByPosition(pos),
|
||||
compareByName)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Clone a list of hints. (Used so that later annotations are not
|
||||
* preserved when scope information changes.)
|
||||
*
|
||||
* @param {Array.<Object>} hints - an array of hint tokens
|
||||
* @return {Array.<Object>} - a new array of objects that are clones of
|
||||
* the objects in the input array
|
||||
*/
|
||||
function copyHints(hints) {
|
||||
function cloneToken(token) {
|
||||
var copy = {},
|
||||
prop;
|
||||
for (prop in token) {
|
||||
if (Object.prototype.hasOwnProperty.call(token, prop)) {
|
||||
copy[prop] = token[prop];
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
return hints.map(cloneToken);
|
||||
}
|
||||
|
||||
var cursor = this.editor.getCursorPos(),
|
||||
offset = this.editor.indexFromPos(cursor),
|
||||
var MAX_DISPLAYED_HINTS = 500,
|
||||
type = this.getType(),
|
||||
association,
|
||||
builtins = this.builtins,
|
||||
hints;
|
||||
|
||||
if (type.property) {
|
||||
hints = copyHints(this.properties);
|
||||
if (type.context &&
|
||||
Object.prototype.hasOwnProperty.call(this.associations, type.context)) {
|
||||
association = this.associations[type.context];
|
||||
hints = HintUtils.annotateWithAssociation(hints, association);
|
||||
hints.sort(compareProperties(association, this.path, offset));
|
||||
} else {
|
||||
hints.sort(compareProperties({}, this.path, offset));
|
||||
}
|
||||
} else {
|
||||
hints = copyHints(this.identifiers);
|
||||
hints = HintUtils.annotateWithScope(hints, this.scope);
|
||||
hints = hints.concat(this.literals);
|
||||
hints.sort(compareIdentifiers(offset));
|
||||
hints = hints.concat(this.globals);
|
||||
hints = hints.concat(HintUtils.LITERALS);
|
||||
hints = hints.concat(HintUtils.KEYWORDS);
|
||||
/**
|
||||
* Is the origin one of the builtin files.
|
||||
*
|
||||
* @param {string} origin
|
||||
*/
|
||||
function isBuiltin(origin) {
|
||||
return builtins.indexOf(origin) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter an array hints using a given query and matcher.
|
||||
* The hints are returned in the format of the matcher.
|
||||
* The matcher returns the value in the "label" property,
|
||||
* the match score in "matchGoodness" property.
|
||||
*
|
||||
* @param {Array} hints - array of hints
|
||||
* @param {StringMatcher} matcher
|
||||
* @returns {Array} - array of matching hints.
|
||||
*/
|
||||
function filterWithQueryAndMatcher(hints, matcher) {
|
||||
var matchResults = $.map(hints, function (hint) {
|
||||
var searchResult = matcher.match(hint.value, query);
|
||||
if (searchResult) {
|
||||
searchResult.value = hint.value;
|
||||
searchResult.guess = hint.guess;
|
||||
if (hint.depth !== undefined) {
|
||||
searchResult.depth = hint.depth;
|
||||
}
|
||||
|
||||
if (!type.property && !type.showFunctionType && hint.origin &&
|
||||
isBuiltin(hint.origin)) {
|
||||
searchResult.builtin = 1;
|
||||
} else {
|
||||
searchResult.builtin = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return searchResult;
|
||||
});
|
||||
|
||||
return matchResults;
|
||||
}
|
||||
|
||||
if (type.property) {
|
||||
hints = this.ternHints || [];
|
||||
hints = filterWithQueryAndMatcher(hints, matcher);
|
||||
|
||||
// If there are no hints then switch over to guesses.
|
||||
if (hints.length === 0) {
|
||||
hints = filterWithQueryAndMatcher(this.ternProperties, matcher);
|
||||
}
|
||||
|
||||
StringMatch.multiFieldSort(hints, { matchGoodness: 0, value: 1 });
|
||||
} else if (type.showFunctionType) {
|
||||
hints = this.getFunctionTypeHint();
|
||||
} else { // identifiers, literals, and keywords
|
||||
hints = this.ternHints || [];
|
||||
hints = hints.concat(HintUtils.LITERALS);
|
||||
hints = hints.concat(HintUtils.KEYWORDS);
|
||||
hints = filterWithQueryAndMatcher(hints, matcher);
|
||||
StringMatch.multiFieldSort(hints, { matchGoodness: 0, depth: 1, builtin: 2, value: 3 });
|
||||
}
|
||||
|
||||
if (hints.length > MAX_DISPLAYED_HINTS) {
|
||||
hints = hints.slice(0, MAX_DISPLAYED_HINTS);
|
||||
}
|
||||
return hints;
|
||||
};
|
||||
|
||||
|
||||
Session.prototype.setTernHints = function (newHints) {
|
||||
this.ternHints = newHints;
|
||||
};
|
||||
Session.prototype.setTernProperties = function (newProperties) {
|
||||
this.ternProperties = newProperties;
|
||||
};
|
||||
Session.prototype.setFnType = function (newFnType) {
|
||||
this.fnType = newFnType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the function type hint. This will format the hint so
|
||||
* that it has the called variable name instead of just "fn()".
|
||||
*/
|
||||
Session.prototype.getFunctionTypeHint = function () {
|
||||
var fnHint = this.fnType,
|
||||
hints = [];
|
||||
|
||||
if (fnHint && (fnHint.substring(0, 3) === "fn(")) {
|
||||
var sessionType = this.getType(),
|
||||
cursor = sessionType.functionCallPos,
|
||||
token = cursor ? this.getToken(cursor) : undefined,
|
||||
varName;
|
||||
if (token) {
|
||||
varName = token.string;
|
||||
if (varName) {
|
||||
fnHint = varName + fnHint.substr(2);
|
||||
}
|
||||
}
|
||||
hints[0] = {value: fnHint, positions: []};
|
||||
}
|
||||
return hints;
|
||||
};
|
||||
|
||||
module.exports = Session;
|
||||
});
|
||||
|
5
src/extensions/default/JavaScriptCodeHints/keyboard.json
Normal file
5
src/extensions/default/JavaScriptCodeHints/keyboard.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"jumptoDefinition": [
|
||||
"Ctrl-J"
|
||||
]
|
||||
}
|
@ -29,20 +29,28 @@ define(function (require, exports, module) {
|
||||
|
||||
var CodeHintManager = brackets.getModule("editor/CodeHintManager"),
|
||||
EditorManager = brackets.getModule("editor/EditorManager"),
|
||||
DocumentManager = brackets.getModule("document/DocumentManager"),
|
||||
Commands = brackets.getModule("command/Commands"),
|
||||
CommandManager = brackets.getModule("command/CommandManager"),
|
||||
Menus = brackets.getModule("command/Menus"),
|
||||
Strings = brackets.getModule("strings"),
|
||||
AppInit = brackets.getModule("utils/AppInit"),
|
||||
ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
|
||||
StringUtils = brackets.getModule("utils/StringUtils"),
|
||||
StringMatch = brackets.getModule("utils/StringMatch"),
|
||||
HintUtils = require("HintUtils"),
|
||||
ScopeManager = require("ScopeManager"),
|
||||
Session = require("Session");
|
||||
|
||||
var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));
|
||||
|
||||
var JUMPTO_DEFINITION = "navigate.jumptoDefinition";
|
||||
|
||||
var session = null, // object that encapsulates the current session state
|
||||
cachedHints = null, // sorted hints for the current hinting session
|
||||
cachedType = null, // describes the lookup type and the object context
|
||||
cachedScope = null, // the inner-most scope returned by the query worker
|
||||
cachedLine = null; // the line number for the cached scope
|
||||
|
||||
var MAX_DISPLAYED_HINTS = 100;
|
||||
cachedLine = null, // the line number for the cached scope
|
||||
matcher = null; // string matcher for hints
|
||||
|
||||
/**
|
||||
* Creates a hint response object. Filters the hint list using the query
|
||||
@ -51,75 +59,14 @@ define(function (require, exports, module) {
|
||||
*
|
||||
* @param {Array.<Object>} hints - hints to be included in the response
|
||||
* @param {string} query - querystring with which to filter the hint list
|
||||
* @param {Object} type - the type of query, property vs. identifier
|
||||
* @return {Object} - hint response as defined by the CodeHintManager API
|
||||
*/
|
||||
function getHintResponse(hints, query) {
|
||||
function getHintResponse(hints, query, type) {
|
||||
|
||||
var trimmedQuery,
|
||||
filteredHints,
|
||||
formattedHints;
|
||||
|
||||
/*
|
||||
* Filter a list of tokens using the query string in the closure.
|
||||
*
|
||||
* @param {Array.<Object>} tokens - list of hints to filter
|
||||
* @param {number} limit - maximum numberof tokens to return
|
||||
* @return {Array.<Object>} - filtered list of hints
|
||||
*/
|
||||
function filterWithQuery(tokens, limit) {
|
||||
|
||||
/*
|
||||
* Filter arr using test, returning at most limit results from the
|
||||
* front of the array.
|
||||
*
|
||||
* @param {Array} arr - array to filter
|
||||
* @param {Function} test - test to determine if an element should
|
||||
* be included in the results
|
||||
* @param {number} limit - the maximum number of elements to return
|
||||
* @return {Array.<Object>} - new array of filtered elements
|
||||
*/
|
||||
function filterArrayPrefix(arr, test, limit) {
|
||||
var i = 0,
|
||||
results = [],
|
||||
elem;
|
||||
|
||||
for (i; i < arr.length && results.length <= limit; i++) {
|
||||
elem = arr[i];
|
||||
if (test(elem)) {
|
||||
results.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// If the query is a string literal (i.e., if it starts with a
|
||||
// string literal delimiter, and hence if trimmedQuery !== query)
|
||||
// then only string literal hints should be returned, and matching
|
||||
// should be performed w.r.t. trimmedQuery. If the query is
|
||||
// otherwise non-empty, no string literals should match. If the
|
||||
// query is empty then no hints are filtered.
|
||||
if (trimmedQuery !== query) {
|
||||
return filterArrayPrefix(tokens, function (token) {
|
||||
if (token.literal && token.kind === "string") {
|
||||
return (token.value.indexOf(trimmedQuery) === 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, limit);
|
||||
} else if (query.length > 0) {
|
||||
return filterArrayPrefix(tokens, function (token) {
|
||||
if (token.literal && token.kind === "string") {
|
||||
return false;
|
||||
} else {
|
||||
return (token.value.indexOf(query) === 0);
|
||||
}
|
||||
}, limit);
|
||||
} else {
|
||||
return tokens.slice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a formatted list of hints with the query substring
|
||||
* highlighted.
|
||||
@ -127,61 +74,55 @@ define(function (require, exports, module) {
|
||||
* @param {Array.<Object>} hints - the list of hints to format
|
||||
* @param {string} query - querystring used for highlighting matched
|
||||
* poritions of each hint
|
||||
* @param {Array.<jQuery.Object>} - array of hints formatted as jQuery
|
||||
* @return {Array.<jQuery.Object>} - array of hints formatted as jQuery
|
||||
* objects
|
||||
*/
|
||||
function formatHints(hints, query) {
|
||||
return hints.map(function (token) {
|
||||
var hint = token.value,
|
||||
index = hint.indexOf(query),
|
||||
$hintObj = $("<span>").addClass("brackets-js-hints"),
|
||||
delimiter = "";
|
||||
var $hintObj = $("<span>").addClass("brackets-js-hints");
|
||||
|
||||
// level indicates either variable scope or property confidence
|
||||
switch (token.level) {
|
||||
case 0:
|
||||
$hintObj.addClass("priority-high");
|
||||
break;
|
||||
case 1:
|
||||
$hintObj.addClass("priority-medium");
|
||||
break;
|
||||
case 2:
|
||||
$hintObj.addClass("priority-low");
|
||||
break;
|
||||
}
|
||||
|
||||
// is the token a global variable?
|
||||
if (token.global) {
|
||||
$hintObj.addClass("global-hint");
|
||||
}
|
||||
|
||||
// is the token a literal?
|
||||
if (token.literal) {
|
||||
$hintObj.addClass("literal-hint");
|
||||
if (token.kind === "string") {
|
||||
delimiter = HintUtils.DOUBLE_QUOTE;
|
||||
if (!type.property && token.depth !== undefined) {
|
||||
switch (token.depth) {
|
||||
case 0:
|
||||
$hintObj.addClass("priority-high");
|
||||
break;
|
||||
case 1:
|
||||
$hintObj.addClass("priority-medium");
|
||||
break;
|
||||
case 2:
|
||||
$hintObj.addClass("priority-low");
|
||||
break;
|
||||
default:
|
||||
$hintObj.addClass("priority-lowest");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (token.guess) {
|
||||
$hintObj.addClass("guess-hint");
|
||||
}
|
||||
|
||||
// is the token a keyword?
|
||||
if (token.keyword) {
|
||||
$hintObj.addClass("keyword-hint");
|
||||
}
|
||||
|
||||
// higlight the matched portion of each hint
|
||||
if (index >= 0) {
|
||||
var prefix = StringUtils.htmlEscape(hint.slice(0, index)),
|
||||
match = StringUtils.htmlEscape(hint.slice(index, index + query.length)),
|
||||
suffix = StringUtils.htmlEscape(hint.slice(index + query.length));
|
||||
|
||||
$hintObj.append(delimiter + prefix)
|
||||
.append($("<span>")
|
||||
.append(match)
|
||||
.addClass("matched-hint"))
|
||||
.append(suffix + delimiter);
|
||||
// highlight the matched portion of each hint
|
||||
if (token.stringRanges) {
|
||||
token.stringRanges.forEach(function (item) {
|
||||
if (item.matched) {
|
||||
$hintObj.append($("<span>")
|
||||
.append(StringUtils.htmlEscape(item.text))
|
||||
.addClass("matched-hint"));
|
||||
} else {
|
||||
$hintObj.append(StringUtils.htmlEscape(item.text));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$hintObj.text(delimiter + hint + delimiter);
|
||||
$hintObj.text(token.value);
|
||||
}
|
||||
|
||||
$hintObj.data("token", token);
|
||||
|
||||
return $hintObj;
|
||||
@ -200,8 +141,11 @@ define(function (require, exports, module) {
|
||||
trimmedQuery = query;
|
||||
}
|
||||
|
||||
filteredHints = filterWithQuery(hints, MAX_DISPLAYED_HINTS);
|
||||
formattedHints = formatHints(filteredHints, trimmedQuery);
|
||||
if (hints) {
|
||||
formattedHints = formatHints(hints, trimmedQuery);
|
||||
} else {
|
||||
formattedHints = [];
|
||||
}
|
||||
|
||||
return {
|
||||
hints: formattedHints,
|
||||
@ -224,25 +168,29 @@ define(function (require, exports, module) {
|
||||
* @return {boolean} - can the provider provide hints for this session?
|
||||
*/
|
||||
JSHints.prototype.hasHints = function (editor, key) {
|
||||
if (session && ((key === null) || HintUtils.maybeIdentifier(key))) {
|
||||
if (session && HintUtils.hintableKey(key)) {
|
||||
var cursor = session.getCursor(),
|
||||
token = session.getToken(cursor);
|
||||
|
||||
// don't autocomplete within strings or comments, etc.
|
||||
if (token && HintUtils.hintable(token)) {
|
||||
var offset = session.getOffset();
|
||||
var offset = session.getOffset(),
|
||||
type = session.getType(),
|
||||
query = session.getQuery();
|
||||
|
||||
// Invalidate cached information if: 1) no scope exists; 2) the
|
||||
// cursor has moved a line; 3) the scope is dirty; or 4) if the
|
||||
// cursor has moved into a different scope. Cached information
|
||||
// is also reset on editor change.
|
||||
if (!cachedScope ||
|
||||
if (!cachedHints ||
|
||||
cachedLine !== cursor.line ||
|
||||
ScopeManager.isScopeDirty(session.editor.document) ||
|
||||
!cachedScope.containsPositionImmediate(offset)) {
|
||||
cachedScope = null;
|
||||
type.property !== cachedType.property ||
|
||||
type.context !== cachedType.context ||
|
||||
type.showFunctionType !== cachedType.showFunctionType) {
|
||||
//console.log("clear hints");
|
||||
cachedLine = null;
|
||||
cachedHints = null;
|
||||
matcher = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -261,61 +209,54 @@ define(function (require, exports, module) {
|
||||
JSHints.prototype.getHints = function (key) {
|
||||
var cursor = session.getCursor(),
|
||||
token = session.getToken(cursor);
|
||||
if ((key === null) || HintUtils.hintable(token)) {
|
||||
if (token) {
|
||||
if (!cachedScope) {
|
||||
var offset = session.getOffset(),
|
||||
scopeResponse = ScopeManager.getScopeInfo(session.editor.document, offset),
|
||||
self = this;
|
||||
if (token && HintUtils.hintableKey(key) && HintUtils.hintable(token)) {
|
||||
var type = session.getType(),
|
||||
query = session.getQuery();
|
||||
|
||||
if (scopeResponse.hasOwnProperty("promise")) {
|
||||
var $deferredHints = $.Deferred();
|
||||
scopeResponse.promise.done(function (scopeInfo) {
|
||||
session.setScopeInfo(scopeInfo);
|
||||
cachedScope = scopeInfo.scope;
|
||||
cachedLine = cursor.line;
|
||||
cachedType = session.getType();
|
||||
cachedHints = session.getHints();
|
||||
// Compute fresh hints if none exist, or if the session
|
||||
// type has changed since the last hint computation
|
||||
if (!cachedHints ||
|
||||
type.property !== cachedType.property ||
|
||||
type.context !== cachedType.context ||
|
||||
type.showFunctionType !== cachedType.showFunctionType || query.length === 0) {
|
||||
var offset = session.getOffset(),
|
||||
scopeResponse = ScopeManager.requestHints(session, session.editor.document, offset),
|
||||
self = this;
|
||||
|
||||
$(self).triggerHandler("resolvedResponse", [cachedHints, cachedType]);
|
||||
|
||||
if ($deferredHints.state() === "pending") {
|
||||
var query = session.getQuery(),
|
||||
hintResponse = getHintResponse(cachedHints, query);
|
||||
|
||||
$deferredHints.resolveWith(null, [hintResponse]);
|
||||
$(self).triggerHandler("hintResponse", [query]);
|
||||
}
|
||||
}).fail(function () {
|
||||
if ($deferredHints.state() === "pending") {
|
||||
$deferredHints.reject();
|
||||
}
|
||||
});
|
||||
|
||||
$(this).triggerHandler("deferredResponse");
|
||||
return $deferredHints;
|
||||
} else {
|
||||
session.setScopeInfo(scopeResponse);
|
||||
cachedScope = scopeResponse.scope;
|
||||
if (scopeResponse.hasOwnProperty("promise")) {
|
||||
var $deferredHints = $.Deferred();
|
||||
scopeResponse.promise.done(function () {
|
||||
cachedLine = cursor.line;
|
||||
}
|
||||
}
|
||||
cachedType = session.getType();
|
||||
matcher = new StringMatch.StringMatcher();
|
||||
cachedHints = session.getHints(query, matcher);
|
||||
|
||||
if (cachedScope) {
|
||||
var type = session.getType(),
|
||||
query = session.getQuery();
|
||||
$(self).triggerHandler("resolvedResponse", [cachedHints, cachedType]);
|
||||
|
||||
// Compute fresh hints if none exist, or if the session
|
||||
// type has changed since the last hint computation
|
||||
if (!cachedHints ||
|
||||
type.property !== cachedType.property ||
|
||||
type.context !== cachedType.context) {
|
||||
cachedType = type;
|
||||
cachedHints = session.getHints();
|
||||
}
|
||||
return getHintResponse(cachedHints, query);
|
||||
if ($deferredHints.state() === "pending") {
|
||||
query = session.getQuery();
|
||||
var hintResponse = getHintResponse(cachedHints, query, type);
|
||||
|
||||
$deferredHints.resolveWith(null, [hintResponse]);
|
||||
$(self).triggerHandler("hintResponse", [query]);
|
||||
}
|
||||
}).fail(function () {
|
||||
if ($deferredHints.state() === "pending") {
|
||||
$deferredHints.reject();
|
||||
}
|
||||
});
|
||||
|
||||
$(this).triggerHandler("deferredResponse");
|
||||
return $deferredHints;
|
||||
} else {
|
||||
cachedLine = cursor.line;
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedHints) {
|
||||
cachedHints = session.getHints(session.getQuery(), matcher);
|
||||
return getHintResponse(cachedHints, query, type);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -336,7 +277,7 @@ define(function (require, exports, module) {
|
||||
query = session.getQuery(),
|
||||
start = {line: cursor.line, ch: cursor.ch - query.length},
|
||||
end = {line: cursor.line, ch: (token ? token.end : cursor.ch)},
|
||||
delimeter;
|
||||
delimiter;
|
||||
|
||||
if (token && token.string === ".") {
|
||||
var nextCursor = session.getNextCursorOnLine(),
|
||||
@ -353,18 +294,23 @@ define(function (require, exports, module) {
|
||||
// to wrap it, preserving the existing delimiter if possible.
|
||||
if (hint.literal && hint.kind === "string") {
|
||||
if (token.string.indexOf(HintUtils.DOUBLE_QUOTE) === 0) {
|
||||
delimeter = HintUtils.DOUBLE_QUOTE;
|
||||
delimiter = HintUtils.DOUBLE_QUOTE;
|
||||
} else if (token.string.indexOf(HintUtils.SINGLE_QUOTE) === 0) {
|
||||
delimeter = HintUtils.SINGLE_QUOTE;
|
||||
delimiter = HintUtils.SINGLE_QUOTE;
|
||||
} else {
|
||||
delimeter = hint.delimeter;
|
||||
delimiter = hint.delimiter;
|
||||
}
|
||||
|
||||
completion = completion.replace("\\", "\\\\");
|
||||
completion = completion.replace(delimeter, "\\" + delimeter);
|
||||
completion = delimeter + completion + delimeter;
|
||||
completion = completion.replace(delimiter, "\\" + delimiter);
|
||||
completion = delimiter + completion + delimiter;
|
||||
}
|
||||
|
||||
if (session.getType().showFunctionType) {
|
||||
// function types show up as hints, so don't insert anything
|
||||
// if we were displaying a function type
|
||||
return false;
|
||||
}
|
||||
// Replace the current token with the completion
|
||||
session.editor.document.replaceRange(completion, start, end);
|
||||
|
||||
@ -385,6 +331,7 @@ define(function (require, exports, module) {
|
||||
function initializeSession(editor) {
|
||||
ScopeManager.handleEditorChange(editor.document);
|
||||
session = new Session(editor);
|
||||
cachedHints = null;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -395,7 +342,6 @@ define(function (require, exports, module) {
|
||||
*/
|
||||
function installEditorListeners(editor) {
|
||||
// always clean up cached scope and hint info
|
||||
cachedScope = null;
|
||||
cachedLine = null;
|
||||
cachedHints = null;
|
||||
cachedType = null;
|
||||
@ -436,6 +382,49 @@ define(function (require, exports, module) {
|
||||
installEditorListeners(current);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle JumptoDefiniton menu/keyboard command.
|
||||
*/
|
||||
function handleJumpToDefinition() {
|
||||
var offset = session.getOffset(),
|
||||
response = ScopeManager.requestJumptoDef(session, session.editor.document, offset);
|
||||
|
||||
if (response.hasOwnProperty("promise")) {
|
||||
response.promise.done(function (jumpResp) {
|
||||
|
||||
if (jumpResp.resultFile) {
|
||||
if (jumpResp.resultFile !== jumpResp.file) {
|
||||
var resolvedPath = ScopeManager.getResolvedPath(jumpResp.resultFile);
|
||||
if (resolvedPath) {
|
||||
CommandManager.execute(Commands.FILE_OPEN, {fullPath: resolvedPath})
|
||||
.done(function () {
|
||||
session.editor.setCursorPos(jumpResp.start);
|
||||
session.editor.setSelection(jumpResp.start, jumpResp.end);
|
||||
session.editor.centerOnCursor();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
session.editor.setCursorPos(jumpResp.start);
|
||||
session.editor.setSelection(jumpResp.start, jumpResp.end);
|
||||
session.editor.centerOnCursor();
|
||||
}
|
||||
}
|
||||
|
||||
}).fail(function () {
|
||||
response.reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register command handler
|
||||
CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, JUMPTO_DEFINITION, handleJumpToDefinition);
|
||||
|
||||
// Add the menu item
|
||||
var menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU);
|
||||
if (menu) {
|
||||
menu.addMenuItem(JUMPTO_DEFINITION, KeyboardPrefs.jumptoDefinition, Menus.BEFORE, Commands.NAVIGATE_GOTO_DEFINITION);
|
||||
}
|
||||
|
||||
ExtensionUtils.loadStyleSheet(module, "styles/brackets-js-hints.css");
|
||||
|
||||
// uninstall/install change listener as the active editor changes
|
||||
@ -452,5 +441,6 @@ define(function (require, exports, module) {
|
||||
// for unit testing
|
||||
exports.jsHintProvider = jsHints;
|
||||
exports.initializeSession = initializeSession;
|
||||
exports.handleJumpToDefinition = handleJumpToDefinition;
|
||||
});
|
||||
});
|
||||
|
@ -1,311 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */
|
||||
/*global self, importScripts, setTimeout */
|
||||
|
||||
/**
|
||||
* Loads a file that contains an AMD module definition using the web worker
|
||||
* importScripts global function.
|
||||
*
|
||||
* @param {string} url - the URL of the module to load
|
||||
* @return {Object} - the module imported from the aforementioned URL
|
||||
*/
|
||||
function require(url) {
|
||||
"use strict";
|
||||
|
||||
var exports = {},
|
||||
oldDefine = self.define;
|
||||
|
||||
/*
|
||||
* The following function is called by AMD modules when loaded with
|
||||
* importScripts. CommonJS-style modules will only pass a wrapper function
|
||||
* as a single argument; proper AMD-style modules may additionally pass an
|
||||
* array of bindings, which are ignored. In the former case, the wrapper
|
||||
* function expects require, exports and module arguments; in the latter
|
||||
* case, the wrapper function expects only an exports arguments.
|
||||
*
|
||||
* @param {?Array.<string>} bindings - an optional list of resources
|
||||
* required by the module
|
||||
* @param {Function} wrapper - the function that defines the module
|
||||
*/
|
||||
self.define = function (bindings, wrapper) {
|
||||
var module = { exports: exports },
|
||||
require = null;
|
||||
|
||||
if (typeof bindings === "function") {
|
||||
wrapper = bindings;
|
||||
} else {
|
||||
require = module.exports;
|
||||
}
|
||||
|
||||
wrapper(require, module.exports, module);
|
||||
exports = module.exports;
|
||||
};
|
||||
self.define.amd = true;
|
||||
importScripts(url);
|
||||
self.define = oldDefine;
|
||||
return exports;
|
||||
}
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var Scope = require("Scope.js"),
|
||||
HintUtils = require("HintUtils.js"),
|
||||
esprima = require("thirdparty/esprima/esprima.js");
|
||||
|
||||
// maximum number of times a file will be reparsed before failing
|
||||
var MAX_RETRIES = 10;
|
||||
|
||||
/**
|
||||
* Send a log message back from the worker to the main thread
|
||||
*
|
||||
* @param {string} msg - the log message
|
||||
*/
|
||||
function _log(msg) {
|
||||
self.postMessage({log: msg });
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the scope to find all the objects of a given type, along with a
|
||||
* list of their positions in the file.
|
||||
*
|
||||
* @param {Function} walk - the function used to walk over an implicit scope
|
||||
* @param {string} keyProp - the property of the implicit scope to walk over
|
||||
* @return {Array.<Object>} - a list of hint tokens for the implicit scope,
|
||||
* including the positions at which they occur.
|
||||
*/
|
||||
function siftPositions(walk, keyProp) {
|
||||
var occurrences,
|
||||
results = [],
|
||||
key,
|
||||
token,
|
||||
comparator = function (a, b) {
|
||||
return a - b;
|
||||
};
|
||||
|
||||
occurrences = walk(function (acc, token) {
|
||||
if (Object.prototype.hasOwnProperty.call(acc, token[keyProp])) {
|
||||
acc[token[keyProp]].push(token.range[0]);
|
||||
} else {
|
||||
acc[token[keyProp]] = [token.range[0]];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (key in occurrences) {
|
||||
if (Object.prototype.hasOwnProperty.call(occurrences, key)) {
|
||||
token = HintUtils.makeToken(key, occurrences[key].sort(comparator));
|
||||
results.push(token);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the scope to compute all available association objects
|
||||
*
|
||||
* @param {Function} walk - the function used to walk over an implicit scope
|
||||
* @return {Object} - a set of association objects: each property is a
|
||||
* property name; each value is an association, which indicates the
|
||||
* number of times the property occurs w.r.t. to a particular lookup
|
||||
* context.
|
||||
*/
|
||||
function siftAssociations(walk) {
|
||||
return walk(function (acc, assoc) {
|
||||
var obj = assoc.object,
|
||||
prop = assoc.property;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(acc, obj.name)) {
|
||||
if (Object.prototype.hasOwnProperty.call(acc[obj.name], prop.name)) {
|
||||
acc[obj.name][prop.name]++;
|
||||
} else {
|
||||
acc[obj.name][prop.name] = 1;
|
||||
}
|
||||
} else {
|
||||
acc[obj.name] = {};
|
||||
acc[obj.name][prop.name] = 1;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSLint globals annotations from an array of JavaScript comments
|
||||
*
|
||||
* @param {Array.<string>} comments - list of JavaScript comments
|
||||
* @return {Array.<Object>} - a list of global hint objects as described by
|
||||
* JSLint annotations found in the comments.
|
||||
*/
|
||||
function extractGlobals(comments) {
|
||||
var globals = [];
|
||||
|
||||
if (comments) {
|
||||
comments.forEach(function (comment) {
|
||||
if (comment.type === "Block" && comment.value) {
|
||||
var val = comment.value;
|
||||
if (val.indexOf("global") === 0) {
|
||||
val.substring(7).split(",").forEach(function (global) {
|
||||
var index = global.indexOf(":");
|
||||
|
||||
if (index >= 0) {
|
||||
global = global.substring(0, index);
|
||||
}
|
||||
globals.push(HintUtils.makeToken(global.trim()));
|
||||
});
|
||||
} else if (val.indexOf("jslint") === 0) {
|
||||
val.substring(7).split(",").forEach(function (ann) {
|
||||
var index = ann.indexOf(":"),
|
||||
aKey = (index >= 0) ? ann.substring(0, index).trim() : "",
|
||||
aVal = (index >= 0) ? ann.substring(index + 1, ann.length).trim() : "";
|
||||
|
||||
if (aVal === "true" && HintUtils.JSL_GLOBAL_DEFS.hasOwnProperty(aKey)) {
|
||||
globals = globals.concat(HintUtils.JSL_GLOBAL_DEFS[aKey]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return globals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the scope and associated parse information back to the caller.
|
||||
* Called by the parse function below.
|
||||
*
|
||||
* @param {string} dir - the directory name of the parsed file
|
||||
* @param {string} file - the file name of the parsed file
|
||||
* @param {number} length - the length of the parsed file
|
||||
* @param {Object} parseObj - scope and token data from the parsed file
|
||||
*/
|
||||
function respond(dir, file, length, parseObj) {
|
||||
var success = !!parseObj,
|
||||
response = {
|
||||
type : HintUtils.SCOPE_MSG_TYPE,
|
||||
dir : dir,
|
||||
file : file,
|
||||
length : length,
|
||||
success : success
|
||||
};
|
||||
|
||||
if (success) {
|
||||
var scope = parseObj.scope,
|
||||
globals = parseObj.globals,
|
||||
identifiers = siftPositions(scope.walkDownIdentifiers.bind(scope), "name"),
|
||||
properties = siftPositions(scope.walkDownProperties.bind(scope), "name"),
|
||||
literals = siftPositions(scope.walkDownLiterals.bind(scope), "value"),
|
||||
associations = siftAssociations(scope.walkDownAssociations.bind(scope));
|
||||
|
||||
response.scope = scope;
|
||||
response.globals = HintUtils.annotateGlobals(globals);
|
||||
response.identifiers = identifiers;
|
||||
response.properties = HintUtils.annotateWithPath(properties, dir, file);
|
||||
response.literals = HintUtils.annotateLiterals(literals, "string");
|
||||
response.associations = associations;
|
||||
}
|
||||
|
||||
self.postMessage(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JavaScript text with Esprima. This function is intended to be
|
||||
* called asynchronously; the respond function above is called with the
|
||||
* results of parsing.
|
||||
*
|
||||
* @param {string} dir - the directory name of the file to parse
|
||||
* @param {string} file - the file name of the file to parse
|
||||
* @param {string} text - the text of the file to parse
|
||||
* @param {number} retries - the number of times an unparseable text should
|
||||
* be retried, after blanking whatever lines are causing the parsing
|
||||
* errors.
|
||||
*/
|
||||
function parse(dir, file, text, retries) {
|
||||
try {
|
||||
var ast = esprima.parse(text, {
|
||||
range : true,
|
||||
tolerant : true,
|
||||
comment : true
|
||||
});
|
||||
|
||||
var scope = new Scope(ast),
|
||||
definedGlobals = extractGlobals(ast.comments),
|
||||
builtinGlobals = HintUtils.BUILTIN_GLOBALS,
|
||||
allGlobals = definedGlobals.concat(builtinGlobals),
|
||||
comparator = function (a, b) { return a.value < b.value; };
|
||||
|
||||
respond(dir, file, text.length, {
|
||||
scope : scope,
|
||||
globals : allGlobals.sort(comparator)
|
||||
});
|
||||
} catch (err) {
|
||||
// If parsing fails, we can try again after blanking out the line
|
||||
// that caused the parse error. This is unreliable though, because
|
||||
// the line number on which the parser fails is not necessarily the
|
||||
// best line to remove. Some errors will cause the entire remainder
|
||||
// of the file to be blanked out, never resulting in a parseable
|
||||
// file. Consequently, this is attempted only when necessary; i.e.,
|
||||
// when the request.force flag is set.
|
||||
// Inspired by fuckit.js: https://github.com/mattdiamond/fuckitjs
|
||||
|
||||
// _log("Parsing failed: " + err + " at " + err.index);
|
||||
if (retries > 0) {
|
||||
var lines = text.split("\n"),
|
||||
lineno = Math.min(lines.length, err.lineNumber) - 1,
|
||||
newline,
|
||||
removed;
|
||||
|
||||
// Blank the offending line and start over
|
||||
if (-1 < lineno < lines.length) {
|
||||
newline = lines[lineno].replace(/./g, " ");
|
||||
if (newline !== lines[lineno]) {
|
||||
removed = lines.splice(lineno, 1, newline);
|
||||
if (removed && removed.length > 0) {
|
||||
setTimeout(function () {
|
||||
parse(dir, file, lines.join("\n"), --retries);
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
respond(dir, file, text.length, null);
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener("message", function (e) {
|
||||
var request = e.data,
|
||||
type = request.type;
|
||||
|
||||
if (type === HintUtils.SCOPE_MSG_TYPE) {
|
||||
var dir = request.dir,
|
||||
file = request.file,
|
||||
text = request.text,
|
||||
retries = request.force ? MAX_RETRIES : 0;
|
||||
setTimeout(function () { parse(dir, file, text, retries); }, 0);
|
||||
} else {
|
||||
_log("Unknown message: " + JSON.stringify(request));
|
||||
}
|
||||
});
|
||||
}());
|
@ -33,16 +33,16 @@
|
||||
color: rgb(0, 0, 100); /* blue */
|
||||
}
|
||||
|
||||
.brackets-js-hints.priority-lowest {
|
||||
color: rgb(0, 0, 0); /* black */
|
||||
}
|
||||
|
||||
.brackets-js-hints.variable-hint {
|
||||
}
|
||||
|
||||
.brackets-js-hints.property-hint {
|
||||
}
|
||||
|
||||
.brackets-js-hints.global-hint {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.brackets-js-hints.literal-hint {
|
||||
color: rgb(50, 50, 50); /* dark grey */
|
||||
}
|
||||
@ -54,3 +54,9 @@
|
||||
.brackets-js-hints .matched-hint {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.brackets-js-hints.guess-hint {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
296
src/extensions/default/JavaScriptCodeHints/tern-worker.js
Normal file
296
src/extensions/default/JavaScriptCodeHints/tern-worker.js
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */
|
||||
/*global self, importScripts, require */
|
||||
|
||||
importScripts("thirdparty/requirejs/require.js");
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var HintUtils;
|
||||
var Tern;
|
||||
require(["./HintUtils"], function (hintUtils) {
|
||||
HintUtils = hintUtils;
|
||||
var ternRequire = require.config({baseUrl: "./thirdparty"});
|
||||
ternRequire(["tern/lib/tern", "tern/plugin/requirejs"], function (tern, requirejs) {
|
||||
Tern = tern;
|
||||
});
|
||||
});
|
||||
|
||||
var ternServer = null;
|
||||
|
||||
// Save the tern callbacks for when we get the contents of the file
|
||||
var fileCallBacks = {};
|
||||
|
||||
/**
|
||||
* Provide the contents of the requested file to tern
|
||||
*/
|
||||
function getFile(name, next) {
|
||||
// save the callback
|
||||
fileCallBacks[name] = next;
|
||||
|
||||
// post a message back to the main thread to get the file contents
|
||||
self.postMessage({
|
||||
type: HintUtils.TERN_GET_FILE_MSG,
|
||||
file: name
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a response from the main thread providing the contents of a file
|
||||
* @param {string} file - the name of the file
|
||||
* @param {string} text - the contents of the file
|
||||
*/
|
||||
function handleGetFile(file, text) {
|
||||
var next = fileCallBacks[file];
|
||||
if (next) {
|
||||
next(null, text);
|
||||
}
|
||||
delete fileCallBacks[file];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new tern server.
|
||||
*/
|
||||
function initTernServer(env, dir, files) {
|
||||
var ternOptions = {
|
||||
defs: env,
|
||||
async: true,
|
||||
getFile: getFile,
|
||||
plugins: {requirejs: {}}
|
||||
};
|
||||
ternServer = new Tern.Server(ternOptions);
|
||||
|
||||
files.forEach(function (file) {
|
||||
ternServer.addFile(file);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function buildRequest(dir, file, query, offset, text) {
|
||||
query = {type: query};
|
||||
query.start = offset;
|
||||
query.end = offset;
|
||||
query.file = file;
|
||||
query.filter = false;
|
||||
query.sort = false;
|
||||
query.depths = true;
|
||||
query.guess = true;
|
||||
query.origins = true;
|
||||
query.expandWordForward = true;
|
||||
|
||||
var request = {query: query, files: [], offset: offset};
|
||||
request.files.push({type: "full", name: file, text: text});
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message back from the worker to the main thread
|
||||
*
|
||||
* @param {string} msg - the log message
|
||||
*/
|
||||
function _log(msg) {
|
||||
self.postMessage({log: msg });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get definition location
|
||||
* @param {string} dir - the directory
|
||||
* @param {string} file - the file name
|
||||
* @param {number} offset - the offset into the file for cursor
|
||||
* @param {string} text - the text of the file
|
||||
*/
|
||||
function getJumptoDef(dir, file, offset, text) {
|
||||
|
||||
var request = buildRequest(dir, file, "definition", offset, text);
|
||||
request.query.lineCharPositions = true;
|
||||
ternServer.request(request, function (error, data) {
|
||||
if (error) {
|
||||
_log("Error returned from Tern 'definition' request: " + error);
|
||||
self.postMessage({type: HintUtils.TERN_JUMPTODEF_MSG});
|
||||
return;
|
||||
}
|
||||
|
||||
// Post a message back to the main thread with the definition
|
||||
self.postMessage({type: HintUtils.TERN_JUMPTODEF_MSG,
|
||||
file: file,
|
||||
resultFile: data.file,
|
||||
offset: offset,
|
||||
start: data.start,
|
||||
end: data.end
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the completions for the given offset
|
||||
* @param {string} dir - the directory
|
||||
* @param {string} file - the file name
|
||||
* @param {number} offset - the offset into the file where we want completions for
|
||||
* @param {string} text - the text of the file
|
||||
*/
|
||||
function getTernHints(dir, file, offset, text) {
|
||||
|
||||
var request = buildRequest(dir, file, "completions", offset, text),
|
||||
i;
|
||||
//_log("request " + dir + " " + file + " " + offset /*+ " " + text */);
|
||||
ternServer.request(request, function (error, data) {
|
||||
var completions = [];
|
||||
if (error) {
|
||||
_log("Error returned from Tern 'completions' request: " + error);
|
||||
} else {
|
||||
//_log("found " + data.completions.length + " for " + file + "@" + offset);
|
||||
for (i = 0; i < data.completions.length; ++i) {
|
||||
var completion = data.completions[i];
|
||||
completions.push({value: completion.name, type: completion.type, depth: completion.depth,
|
||||
guess: completion.guess, origin: completion.origin});
|
||||
}
|
||||
}
|
||||
|
||||
// Post a message back to the main thread with the completions
|
||||
self.postMessage({type: HintUtils.TERN_COMPLETIONS_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
offset: offset,
|
||||
completions: completions
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the known properties for guessing.
|
||||
*
|
||||
* @param {string} dir - the directory
|
||||
* @param {string} file - the file name
|
||||
* @param {string} text - the text of the file
|
||||
*/
|
||||
function handleGetProperties(dir, file, offset, text) {
|
||||
|
||||
var request = buildRequest(dir, file, "properties", undefined, text),
|
||||
i;
|
||||
//_log("request " + request.type + dir + " " + file);
|
||||
ternServer.request(request, function (error, data) {
|
||||
var properties = [];
|
||||
if (error) {
|
||||
_log("Error returned from Tern 'properties' request: " + error);
|
||||
} else {
|
||||
//_log("completions = " + data.completions.length);
|
||||
for (i = 0; i < data.completions.length; ++i) {
|
||||
var property = data.completions[i];
|
||||
properties.push({value: property, guess: true});
|
||||
}
|
||||
}
|
||||
|
||||
// Post a message back to the main thread with the completions
|
||||
self.postMessage({type: HintUtils.TERN_GET_PROPERTIES_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
offset: offset,
|
||||
properties: properties
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the function type for the given offset
|
||||
* @param {string} dir - the directory
|
||||
* @param {string} file - the file name
|
||||
* @param {number} offset - the offset into the file where we want completions for
|
||||
* @param {string} text - the text of the file
|
||||
*/
|
||||
function handleFunctionType(dir, file, pos, offset, text) {
|
||||
|
||||
var request = buildRequest(dir, file, "type", pos, text);
|
||||
|
||||
request.preferFunction = true;
|
||||
|
||||
//_log("request " + dir + " " + file + " " + offset /*+ " " + text */);
|
||||
ternServer.request(request, function (error, data) {
|
||||
var fnType = "";
|
||||
if (error) {
|
||||
_log("Error returned from Tern 'type' request: " + error);
|
||||
} else {
|
||||
fnType = data.type;
|
||||
}
|
||||
|
||||
// Post a message back to the main thread with the completions
|
||||
self.postMessage({type: HintUtils.TERN_CALLED_FUNC_TYPE_MSG,
|
||||
dir: dir,
|
||||
file: file,
|
||||
offset: offset,
|
||||
fnType: fnType
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
self.addEventListener("message", function (e) {
|
||||
var dir, file, text, offset,
|
||||
request = e.data,
|
||||
type = request.type;
|
||||
|
||||
if (type === HintUtils.TERN_INIT_MSG) {
|
||||
|
||||
dir = request.dir;
|
||||
var env = request.env,
|
||||
files = request.files;
|
||||
initTernServer(env, dir, files);
|
||||
|
||||
} else if (type === HintUtils.TERN_COMPLETIONS_MSG) {
|
||||
dir = request.dir;
|
||||
file = request.file;
|
||||
text = request.text;
|
||||
offset = request.offset;
|
||||
getTernHints(dir, file, offset, text);
|
||||
} else if (type === HintUtils.TERN_GET_PROPERTIES_MSG) {
|
||||
file = request.file;
|
||||
dir = request.dir;
|
||||
text = request.text;
|
||||
offset = request.offset;
|
||||
handleGetProperties(dir, file, offset, text);
|
||||
} else if (type === HintUtils.TERN_GET_FILE_MSG) {
|
||||
file = request.file;
|
||||
text = request.text;
|
||||
handleGetFile(file, text);
|
||||
} else if (type === HintUtils.TERN_CALLED_FUNC_TYPE_MSG) {
|
||||
dir = request.dir;
|
||||
file = request.file;
|
||||
text = request.text;
|
||||
offset = request.offset;
|
||||
var pos = request.pos;
|
||||
handleFunctionType(dir, file, pos, offset, text);
|
||||
} else if (type === HintUtils.TERN_JUMPTODEF_MSG) {
|
||||
file = request.file;
|
||||
dir = request.dir;
|
||||
text = request.text;
|
||||
offset = request.offset;
|
||||
getJumptoDef(dir, file, offset, text);
|
||||
} else {
|
||||
_log("Unknown message: " + JSON.stringify(request));
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
10
src/extensions/default/JavaScriptCodeHints/test/MyModule.js
Normal file
10
src/extensions/default/JavaScriptCodeHints/test/MyModule.js
Normal file
@ -0,0 +1,10 @@
|
||||
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
|
||||
/*global define, $ */
|
||||
define(function (require, exports, module) {
|
||||
"use strict";
|
||||
exports.a = function (a, b) {};
|
||||
exports.b = function () {
|
||||
return "a string";
|
||||
};
|
||||
exports.j = "hi";
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
|
||||
/*global brackets, $ */
|
||||
/*global brackets, require */
|
||||
|
||||
var A1 = { propA : 1 },
|
||||
A2 = { propA : 2 },
|
||||
@ -20,4 +20,26 @@ function funB(paramB1, paramB2) {
|
||||
B1.foo = 0;
|
||||
console.log("hello\\\" world!");
|
||||
}
|
||||
|
||||
var s = "a string";
|
||||
var r = s;
|
||||
var t = r;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} a
|
||||
* @param {number} b
|
||||
*/
|
||||
function funD(a, b) {
|
||||
"use strict";
|
||||
return {x: a, y: b};
|
||||
}
|
||||
|
||||
require(["MyModule"], function (myModule) {
|
||||
'use strict';
|
||||
var x = myModule.a;
|
||||
});
|
||||
|
||||
funB();
|
||||
var x = A1;
|
1
src/extensions/default/JavaScriptCodeHints/thirdparty/acorn
vendored
Submodule
1
src/extensions/default/JavaScriptCodeHints/thirdparty/acorn
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f3c70d76efd79040721f913b5877a4f3149fdbe2
|
@ -1,19 +0,0 @@
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
File diff suppressed because one or more lines are too long
58
src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/LICENSE
vendored
Normal file
58
src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/LICENSE
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
RequireJS is released under two licenses: new BSD, and MIT. You may pick the
|
||||
license that best suits your development needs. The text of both licenses are
|
||||
provided below.
|
||||
|
||||
|
||||
The "New" BSD License:
|
||||
----------------------
|
||||
|
||||
Copyright (c) 2010-2011, The Dojo Foundation
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of the Dojo Foundation nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
MIT License
|
||||
-----------
|
||||
|
||||
Copyright (c) 2010-2011, The Dojo Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
34
src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/require.js
vendored
Normal file
34
src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/require.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
RequireJS 2.1.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
|
||||
Available via the MIT or new BSD license.
|
||||
see: http://github.com/jrburke/requirejs for details
|
||||
*/
|
||||
var requirejs,require,define;
|
||||
(function(W){function D(b){return M.call(b)==="[object Function]"}function E(b){return M.call(b)==="[object Array]"}function t(b,c){if(b){var d;for(d=0;d<b.length;d+=1)if(b[d]&&c(b[d],d,b))break}}function N(b,c){if(b){var d;for(d=b.length-1;d>-1;d-=1)if(b[d]&&c(b[d],d,b))break}}function A(b,c){for(var d in b)if(b.hasOwnProperty(d)&&c(b[d],d))break}function O(b,c,d,g){c&&A(c,function(c,j){if(d||!F.call(b,j))g&&typeof c!=="string"?(b[j]||(b[j]={}),O(b[j],c,d,g)):b[j]=c});return b}function r(b,c){return function(){return c.apply(b,
|
||||
arguments)}}function X(b){if(!b)return b;var c=W;t(b.split("."),function(b){c=c[b]});return c}function G(b,c,d,g){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=g;if(d)c.originalError=d;return c}function ba(){if(H&&H.readyState==="interactive")return H;N(document.getElementsByTagName("script"),function(b){if(b.readyState==="interactive")return H=b});return H}var g,s,u,y,q,B,H,I,Y,Z,ca=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,da=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
|
||||
$=/\.js$/,ea=/^\.\//;s=Object.prototype;var M=s.toString,F=s.hasOwnProperty,fa=Array.prototype.splice,v=!!(typeof window!=="undefined"&&navigator&&document),aa=!v&&typeof importScripts!=="undefined",ga=v&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,R=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",w={},n={},P=[],J=!1;if(typeof define==="undefined"){if(typeof requirejs!=="undefined"){if(D(requirejs))return;n=requirejs;requirejs=void 0}typeof require!=="undefined"&&
|
||||
!D(require)&&(n=require,require=void 0);g=requirejs=function(b,c,d,p){var i,j="_";!E(b)&&typeof b!=="string"&&(i=b,E(c)?(b=c,c=d,d=p):b=[]);if(i&&i.context)j=i.context;(p=w[j])||(p=w[j]=g.s.newContext(j));i&&p.configure(i);return p.require(b,c,d)};g.config=function(b){return g(b)};g.nextTick=typeof setTimeout!=="undefined"?function(b){setTimeout(b,4)}:function(b){b()};require||(require=g);g.version="2.1.1";g.jsExtRegExp=/^\/|:|\?|\.js$/;g.isBrowser=v;s=g.s={contexts:w,newContext:function(b){function c(a,
|
||||
f,x){var e,m,b,c,d,h,i,g=f&&f.split("/");e=g;var j=k.map,l=j&&j["*"];if(a&&a.charAt(0)===".")if(f){e=k.pkgs[f]?g=[f]:g.slice(0,g.length-1);f=a=e.concat(a.split("/"));for(e=0;f[e];e+=1)if(m=f[e],m===".")f.splice(e,1),e-=1;else if(m==="..")if(e===1&&(f[2]===".."||f[0]===".."))break;else e>0&&(f.splice(e-1,2),e-=2);e=k.pkgs[f=a[0]];a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else a.indexOf("./")===0&&(a=a.substring(2));if(x&&(g||l)&&j){f=a.split("/");for(e=f.length;e>0;e-=1){b=f.slice(0,e).join("/");if(g)for(m=
|
||||
g.length;m>0;m-=1)if(x=j[g.slice(0,m).join("/")])if(x=x[b]){c=x;d=e;break}if(c)break;!h&&l&&l[b]&&(h=l[b],i=e)}!c&&h&&(c=h,d=i);c&&(f.splice(0,d,c),a=f.join("/"))}return a}function d(a){v&&t(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===h.contextName)return f.parentNode.removeChild(f),!0})}function p(a){var f=k.paths[a];if(f&&E(f)&&f.length>1)return d(a),f.shift(),h.require.undef(a),h.require([a]),!0}function i(a){var f,
|
||||
b=a?a.indexOf("!"):-1;b>-1&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function j(a,f,b,e){var m,K,d=null,g=f?f.name:null,j=a,l=!0,k="";a||(l=!1,a="_@r"+(M+=1));a=i(a);d=a[0];a=a[1];d&&(d=c(d,g,e),K=o[d]);a&&(d?k=K&&K.normalize?K.normalize(a,function(a){return c(a,g,e)}):c(a,g,e):(k=c(a,g,e),a=i(k),d=a[0],k=a[1],b=!0,m=h.nameToUrl(k)));b=d&&!K&&!b?"_unnormalized"+(N+=1):"";return{prefix:d,name:k,parentMap:f,unnormalized:!!b,url:m,originalName:j,isDefine:l,id:(d?d+"!"+k:k)+b}}function n(a){var f=
|
||||
a.id,b=l[f];b||(b=l[f]=new h.Module(a));return b}function q(a,f,b){var e=a.id,m=l[e];if(F.call(o,e)&&(!m||m.defineEmitComplete))f==="defined"&&b(o[e]);else n(a).on(f,b)}function z(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(t(b,function(f){if(f=l[f])f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)g.onError(a)}function s(){P.length&&(fa.apply(C,[C.length-1,0].concat(P)),P=[])}function u(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,t(a.depMaps,function(e,c){var d=e.id,
|
||||
g=l[d];g&&!a.depMatched[c]&&!b[d]&&(f[d]?(a.defineDep(c,o[d]),a.check()):u(g,f,b))}),b[e]=!0)}function w(){var a,f,b,e,m=(b=k.waitSeconds*1E3)&&h.startTime+b<(new Date).getTime(),c=[],g=[],i=!1,j=!0;if(!S){S=!0;A(l,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||g.push(b),!b.error))if(!b.inited&&m)p(f)?i=e=!0:(c.push(f),d(f));else if(!b.inited&&b.fetched&&a.isDefine&&(i=!0,!a.prefix))return j=!1});if(m&&c.length)return b=G("timeout","Load timeout for modules: "+c,null,c),b.contextName=h.contextName,
|
||||
z(b);j&&t(g,function(a){u(a,{},{})});if((!m||e)&&i)if((v||aa)&&!T)T=setTimeout(function(){T=0;w()},50);S=!1}}function y(a){n(j(a[0],null,!0)).init(a[1],a[2])}function B(a){var a=a.currentTarget||a.srcElement,b=h.onScriptLoad;a.detachEvent&&!R?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=h.onScriptError;a.detachEvent&&!R||a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function I(){var a;for(s();C.length;)if(a=C.shift(),a[0]===
|
||||
null)return z(G("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));else y(a)}var S,U,h,L,T,k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},map:{},config:{}},l={},V={},C=[],o={},Q={},M=1,N=1;L={require:function(a){return a.require?a.require:a.require=h.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=o[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&
|
||||
k.config[a.map.id]||{}},exports:o[a.map.id]}}};U=function(a){this.events=V[a.id]||{};this.map=a;this.shim=k.shim[a.id];this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};U.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=r(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},
|
||||
defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;h.startTime=(new Date).getTime();var a=this.map;if(this.shim)h.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],r(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;Q[a]||(Q[a]=!0,h.load(this.map.id,a))},check:function(){if(this.enabled&&
|
||||
!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,m=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(this.depCount<1&&!this.defined){if(D(m)){if(this.events.error)try{e=h.execCb(c,m,b,e)}catch(d){a=d}else e=h.execCb(c,m,b,e);if(this.map.isDefine)if((b=this.module)&&b.exports!==void 0&&b.exports!==this.exports)e=b.exports;else if(e===void 0&&this.usingExports)e=this.exports;if(a)return a.requireMap=this.map,
|
||||
a.requireModules=[this.map.id],a.requireType="define",z(this.error=a)}else e=m;this.exports=e;if(this.map.isDefine&&!this.ignore&&(o[c]=e,g.onResourceLoad))g.onResourceLoad(h,this.map,this.depMaps);delete l[c];this.defined=!0}this.defining=!1;if(this.defined&&!this.defineEmitted)this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);q(d,"defined",r(this,function(e){var m,
|
||||
d;d=this.map.name;var x=this.map.parentMap?this.map.parentMap.name:null,i=h.makeRequire(a.parentMap,{enableBuildCallback:!0,skipMap:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,x,!0)})||""),e=j(a.prefix+"!"+d,this.map.parentMap),q(e,"defined",r(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=l[e.id]){this.depMaps.push(e);if(this.events.error)d.on("error",r(this,function(a){this.emit("error",a)}));d.enable()}}else m=r(this,
|
||||
function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=r(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];A(l,function(a){a.map.id.indexOf(b+"_unnormalized")===0&&delete l[a.map.id]});z(a)}),m.fromText=r(this,function(b,e){var f=a.name,c=j(f),d=J;e&&(b=e);d&&(J=!1);n(c);try{g.exec(b)}catch(x){throw Error("fromText eval for "+f+" failed: "+x);}d&&(J=!0);this.depMaps.push(c);h.completeLoad(f);i([f],m)}),e.load(a.name,i,m,k)}));h.enable(d,this);this.pluginMaps[d.id]=
|
||||
d},enable:function(){this.enabling=this.enabled=!0;t(this.depMaps,r(this,function(a,b){var c,e;if(typeof a==="string"){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=L[a.id]){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",r(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&q(a,"error",this.errback)}c=a.id;e=l[c];!L[c]&&e&&!e.enabled&&h.enable(a,this)}));A(this.pluginMaps,r(this,function(a){var b=l[a.id];b&&!b.enabled&&
|
||||
h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){t(this.events[a],function(a){a(b)});a==="error"&&delete this.events[a]}};h={config:k,contextName:b,registry:l,defined:o,urlFetched:Q,defQueue:C,Module:U,makeModuleMap:j,nextTick:g.nextTick,configure:function(a){a.baseUrl&&a.baseUrl.charAt(a.baseUrl.length-1)!=="/"&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};A(a,function(a,b){e[b]?
|
||||
b==="map"?O(k[b],a,!0,!0):O(k[b],a,!0):k[b]=a});if(a.shim)A(a.shim,function(a,b){E(a)&&(a={deps:a});if(a.exports&&!a.exportsFn)a.exportsFn=h.makeShimExports(a);c[b]=a}),k.shim=c;if(a.packages)t(a.packages,function(a){a=typeof a==="string"?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ea,"").replace($,"")}}),k.pkgs=b;A(l,function(a,b){if(!a.inited&&!a.map.unnormalized)a.map=j(b)});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;
|
||||
a.init&&(b=a.init.apply(W,arguments));return b||X(a.exports)}},makeRequire:function(a,f){function d(e,c,i){var k,p;if(f.enableBuildCallback&&c&&D(c))c.__requireJsBuild=!0;if(typeof e==="string"){if(D(c))return z(G("requireargs","Invalid require call"),i);if(a&&L[e])return L[e](l[a.id]);if(g.get)return g.get(h,e,a);k=j(e,a,!1,!0);k=k.id;return!F.call(o,k)?z(G("notloaded",'Module name "'+k+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):o[k]}I();h.nextTick(function(){I();p=
|
||||
n(j(null,a));p.skipMap=f.skipMap;p.init(e,c,i,{enabled:!0});w()});return d}f=f||{};O(d,{isBrowser:v,toUrl:function(b){var d=b.lastIndexOf("."),f=null;d!==-1&&(f=b.substring(d,b.length),b=b.substring(0,d));return h.nameToUrl(c(b,a&&a.id,!0),f)},defined:function(b){b=j(b,a,!1,!0).id;return F.call(o,b)},specified:function(b){b=j(b,a,!1,!0).id;return F.call(o,b)||F.call(l,b)}});if(!a)d.undef=function(b){s();var c=j(b,a,!0),d=l[b];delete o[b];delete Q[c.url];delete V[b];if(d){if(d.events.defined)V[b]=
|
||||
d.events;delete l[b]}};return d},enable:function(a){l[a.id]&&n(a).enable()},completeLoad:function(a){var b,c,d=k.shim[a]||{},g=d.exports;for(s();C.length;){c=C.shift();if(c[0]===null){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);y(c)}c=l[a];if(!b&&!o[a]&&c&&!c.inited)if(k.enforceDefine&&(!g||!X(g)))if(p(a))return;else return z(G("nodefine","No define call for "+a,null,[a]));else y([a,d.deps||[],d.exportsFn]);w()},nameToUrl:function(a,b){var c,d,i,h,j,l;if(g.jsExtRegExp.test(a))h=a+(b||"");else{c=
|
||||
k.paths;d=k.pkgs;h=a.split("/");for(j=h.length;j>0;j-=1)if(l=h.slice(0,j).join("/"),i=d[l],l=c[l]){E(l)&&(l=l[0]);h.splice(0,j,l);break}else if(i){c=a===i.name?i.location+"/"+i.main:i.location;h.splice(0,j,c);break}h=h.join("/");h+=b||(/\?/.test(h)?"":".js");h=(h.charAt(0)==="/"||h.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+h}return k.urlArgs?h+((h.indexOf("?")===-1?"?":"&")+k.urlArgs):h},load:function(a,b){g.load(h,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if(a.type===
|
||||
"load"||ga.test((a.currentTarget||a.srcElement).readyState))H=null,a=B(a),h.completeLoad(a.id)},onScriptError:function(a){var b=B(a);if(!p(b.id))return z(G("scripterror","Script error",a,[b.id]))}};h.require=h.makeRequire();return h}};g({});t(["toUrl","undef","defined","specified"],function(b){g[b]=function(){var c=w._;return c.require[b].apply(c,arguments)}});if(v&&(u=s.head=document.getElementsByTagName("head")[0],y=document.getElementsByTagName("base")[0]))u=s.head=y.parentNode;g.onError=function(b){throw b;
|
||||
};g.load=function(b,c,d){var g=b&&b.config||{},i;if(v)return i=g.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),i.type=g.scriptType||"text/javascript",i.charset="utf-8",i.async=!0,i.setAttribute("data-requirecontext",b.contextName),i.setAttribute("data-requiremodule",c),i.attachEvent&&!(i.attachEvent.toString&&i.attachEvent.toString().indexOf("[native code")<0)&&!R?(J=!0,i.attachEvent("onreadystatechange",b.onScriptLoad)):(i.addEventListener("load",
|
||||
b.onScriptLoad,!1),i.addEventListener("error",b.onScriptError,!1)),i.src=d,I=i,y?u.insertBefore(i,y):u.appendChild(i),I=null,i;else aa&&(importScripts(d),b.completeLoad(c))};v&&N(document.getElementsByTagName("script"),function(b){if(!u)u=b.parentNode;if(q=b.getAttribute("data-main")){if(!n.baseUrl)B=q.split("/"),Y=B.pop(),Z=B.length?B.join("/")+"/":"./",n.baseUrl=Z,q=Y;q=q.replace($,"");n.deps=n.deps?n.deps.concat(q):[q];return!0}});define=function(b,c,d){var g,i;typeof b!=="string"&&(d=c,c=b,b=
|
||||
null);E(c)||(d=c,c=[]);!c.length&&D(d)&&d.length&&(d.toString().replace(ca,"").replace(da,function(b,d){c.push(d)}),c=(d.length===1?["require"]:["require","exports","module"]).concat(c));if(J&&(g=I||ba()))b||(b=g.getAttribute("data-requiremodule")),i=w[g.getAttribute("data-requirecontext")];(i?i.defQueue:P).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(n)}})(this);
|
1
src/extensions/default/JavaScriptCodeHints/thirdparty/tern
vendored
Submodule
1
src/extensions/default/JavaScriptCodeHints/thirdparty/tern
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 14a0211b6af41b05096413e1712eafe367ac0bb1
|
@ -226,6 +226,23 @@ define(function (require, exports, module) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of a string in a list of hints.
|
||||
* @param {Array} hintList - the list of hints
|
||||
* @param {string} hintSelection - the string represenation of the hint
|
||||
* to find the index of
|
||||
* @return {number} the index of the hint corresponding to the hintSelection
|
||||
*/
|
||||
function findHint(hintList, hintSelection) {
|
||||
var i, l;
|
||||
for (i = 0, l = hintList.length; i < l; ++i) {
|
||||
var current = hintList[i].data("token");
|
||||
if (hintSelection === current.value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* Simulation of selection of a particular hint in a hint list.
|
||||
* Presumably results in side effects in the hint provider's
|
||||
@ -234,18 +251,55 @@ define(function (require, exports, module) {
|
||||
* @param {Object} provider - a CodeHint provider object
|
||||
* @param {Object} hintObj - a hint response object from that provider,
|
||||
* possibly deferred
|
||||
* @param {number} index - the index into the hint list at which a hint
|
||||
* is to be selected
|
||||
* @param {string} hintSelection - the hint to select
|
||||
*/
|
||||
function selectHint(provider, hintObj, index) {
|
||||
function selectHint(provider, hintObj, hintSelection) {
|
||||
var hintList = expectHints(provider);
|
||||
_waitForHints(hintObj, function (hintList) {
|
||||
expect(hintList).not.toBeNull();
|
||||
var index = findHint(hintList, hintSelection);
|
||||
expect(hintList[index].data("token")).not.toBeNull();
|
||||
expect(provider.insertHint(hintList[index])).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the editor to change positions, such as after a jump to
|
||||
* definition has been triggered. Will timeout after 3 seconds
|
||||
*
|
||||
* @param {{line:number, ch:number}} oldLocation - the original line/col
|
||||
* @param {Function} callback - the callback to apply once the editor has changed position
|
||||
*/
|
||||
function _waitForJump(oldLocation, callback) {
|
||||
waitsFor(function () {
|
||||
var cursor = testEditor.getCursorPos();
|
||||
return (cursor.line !== oldLocation.line) ||
|
||||
(cursor.ch !== oldLocation.ch);
|
||||
}, "Expected jump did not occur", 3000);
|
||||
|
||||
runs(function () { callback(testEditor.getCursorPos()); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a jump to definition, and verify that the editor jumped to
|
||||
* the expected location.
|
||||
*
|
||||
* @param {{line:number, ch:number}} expectedLocation - the location the editor should
|
||||
* jump to.
|
||||
*/
|
||||
function editorJumped(expectedLocation) {
|
||||
var oldLocation = testEditor.getCursorPos();
|
||||
|
||||
JSCodeHints.handleJumpToDefinition();
|
||||
|
||||
|
||||
_waitForJump(oldLocation, function (newCursor) {
|
||||
expect(newCursor.line).toBe(expectedLocation.line);
|
||||
expect(newCursor.ch).toBe(expectedLocation.ch);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
describe("JavaScript Code Hinting", function () {
|
||||
|
||||
beforeEach(function () {
|
||||
@ -278,13 +332,13 @@ define(function (require, exports, module) {
|
||||
it("should list declared variable and function names in outer scope", function () {
|
||||
testEditor.setCursorPos({ line: 6, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresentExact(hintObj, ["A2", "A3", "funB", "A1"]);
|
||||
hintsPresent(hintObj, ["A2", "A3", "funB", "A1"]);
|
||||
});
|
||||
|
||||
it("should filter hints by query", function () {
|
||||
testEditor.setCursorPos({ line: 5, ch: 10 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresentExact(hintObj, ["A2", "A3", "A1"]);
|
||||
hintsPresent(hintObj, ["A1", "A2", "A3"]);
|
||||
hintsAbsent(hintObj, ["funB"]);
|
||||
});
|
||||
|
||||
@ -293,7 +347,7 @@ define(function (require, exports, module) {
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresent(hintObj, ["break", "case", "catch"]);
|
||||
});
|
||||
|
||||
/*
|
||||
it("should list explicitly defined globals from JSLint annotations", function () {
|
||||
testEditor.setCursorPos({ line: 6, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
@ -305,7 +359,7 @@ define(function (require, exports, module) {
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresent(hintObj, ["alert", "console", "confirm", "navigator", "window", "frames"]);
|
||||
});
|
||||
|
||||
*/
|
||||
it("should NOT list implicitly defined globals from missing JSLint annotations", function () {
|
||||
testEditor.setCursorPos({ line: 6, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
@ -339,7 +393,7 @@ define(function (require, exports, module) {
|
||||
it("should NOT list variables, function names and parameter names in other files", function () {
|
||||
testEditor.setCursorPos({ line: 6, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsAbsent(hintObj, ["D1", "D2", "funE", "E1", "E2"]);
|
||||
hintsAbsent(hintObj, ["E1", "E2"]);
|
||||
});
|
||||
|
||||
it("should NOT list property names on value lookups", function () {
|
||||
@ -351,31 +405,31 @@ define(function (require, exports, module) {
|
||||
it("should list declared variable, function and parameter names in inner scope", function () {
|
||||
testEditor.setCursorPos({ line: 12, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresentExact(hintObj, ["funC", "B2", "B1", "paramB2", "paramB1", "funB", "A2", "A3", "A1"]);
|
||||
hintsPresent(hintObj, ["B1", "B2", "funC", "paramB1", "paramB2", "funB", "A1", "A2", "A3"]);
|
||||
});
|
||||
|
||||
/*
|
||||
it("should list string literals that occur in the file", function () {
|
||||
testEditor.setCursorPos({ line: 12, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresent(hintObj, ["use strict"]);
|
||||
});
|
||||
|
||||
*/
|
||||
it("should NOT list string literals from other files", function () {
|
||||
testEditor.setCursorPos({ line: 6, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsAbsent(hintObj, ["a very nice string"]);
|
||||
});
|
||||
|
||||
it("should list property names that occur in the file", function () {
|
||||
it("should list property names that have been declared in the file", function () {
|
||||
testEditor.setCursorPos({ line: 17, ch: 11 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresent(hintObj, ["propA", "propB", "propC"]);
|
||||
hintsPresent(hintObj, ["propB"]);
|
||||
});
|
||||
|
||||
it("should list property names that occur in other files", function () {
|
||||
testEditor.setCursorPos({ line: 17, ch: 11 });
|
||||
it("should list identifier names that occur in other files", function () {
|
||||
testEditor.setCursorPos({ line: 16, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresent(hintObj, ["propD", "propE"]);
|
||||
hintsPresent(hintObj, ["D1", "D2"]);
|
||||
});
|
||||
|
||||
it("should NOT list variable, parameter or function names on property lookups", function () {
|
||||
@ -398,7 +452,7 @@ define(function (require, exports, module) {
|
||||
it("should list explicit hints for variable and function names", function () {
|
||||
testEditor.setCursorPos({ line: 6, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider, null);
|
||||
hintsPresentExact(hintObj, ["A2", "A3", "funB", "A1"]);
|
||||
hintsPresent(hintObj, ["A2", "A3", "funB", "A1"]);
|
||||
});
|
||||
|
||||
it("should list implicit hints when typing property lookups", function () {
|
||||
@ -406,6 +460,8 @@ define(function (require, exports, module) {
|
||||
expectHints(JSCodeHints.jsHintProvider, ".");
|
||||
});
|
||||
|
||||
/* Single quote and double quote keys cause hasHints() to return false.
|
||||
It used to return true when string literals were supported.
|
||||
it("should list implicit hints when typing string literals (single quote)", function () {
|
||||
testEditor.setCursorPos({ line: 9, ch: 0 });
|
||||
expectHints(JSCodeHints.jsHintProvider, "'");
|
||||
@ -415,25 +471,24 @@ define(function (require, exports, module) {
|
||||
testEditor.setCursorPos({ line: 9, ch: 0 });
|
||||
expectHints(JSCodeHints.jsHintProvider, "\"");
|
||||
});
|
||||
|
||||
it("should give priority to property names associated with the current context", function () {
|
||||
testEditor.setCursorPos({ line: 19, ch: 11 });
|
||||
*/
|
||||
it("should give priority to identifier names associated with the current context", function () {
|
||||
testEditor.setCursorPos({ line: 16, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresentOrdered(hintObj, ["propB", "propA"]);
|
||||
hintsPresentOrdered(hintObj, ["propB", "propC"]);
|
||||
hintsPresentOrdered(hintObj, ["C1", "B1"]);
|
||||
hintsPresentOrdered(hintObj, ["C2", "B2"]);
|
||||
});
|
||||
|
||||
it("should give priority to property names associated with the current context from other files", function () {
|
||||
testEditor.setCursorPos({ line: 20, ch: 16 });
|
||||
testEditor.setCursorPos({ line: 16, ch: 0 });
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresentOrdered(hintObj, ["log", "propA"]);
|
||||
hintsPresentOrdered(hintObj, ["log", "propB"]);
|
||||
hintsPresentOrdered(hintObj, ["log", "propC"]);
|
||||
hintsPresentOrdered(hintObj, ["log", "propD"]);
|
||||
hintsPresentOrdered(hintObj, ["log", "propE"]);
|
||||
hintsPresentOrdered(hintObj, ["C1", "D1"]);
|
||||
hintsPresentOrdered(hintObj, ["B1", "D1"]);
|
||||
hintsPresentOrdered(hintObj, ["A1", "D1"]);
|
||||
hintsPresentOrdered(hintObj, ["funB", "funE"]);
|
||||
});
|
||||
|
||||
it("should choose the correct delimiter for string literal hints with no query", function () {
|
||||
/* it("should choose the correct delimiter for string literal hints with no query", function () {
|
||||
var start = { line: 18, ch: 0 },
|
||||
end = { line: 18, ch: 18 };
|
||||
|
||||
@ -445,17 +500,17 @@ define(function (require, exports, module) {
|
||||
expect(testDoc.getRange(start, end)).toEqual('"hello\\\\\\" world!"');
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
it("should insert value hints with no current query", function () {
|
||||
var start = { line: 6, ch: 0 },
|
||||
end = { line: 6, ch: 4 };
|
||||
end = { line: 6, ch: 2 };
|
||||
|
||||
testEditor.setCursorPos(start);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 2); // hint 2 is "funB"
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "A2"); // hint 2 is "A2"
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
expect(testDoc.getRange(start, end)).toEqual("funB");
|
||||
//expect(testEditor.getCursorPos()).toEqual(end);
|
||||
expect(testDoc.getRange(start, end)).toEqual("A2");
|
||||
});
|
||||
});
|
||||
|
||||
@ -466,10 +521,10 @@ define(function (require, exports, module) {
|
||||
|
||||
testEditor.setCursorPos(start);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
hintsPresentExact(hintObj, ["A2", "A3", "A1"]);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 2); // hint 2 is "A1"
|
||||
hintsPresent(hintObj, ["A1", "A2", "A3"]);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "A1"); // hint 1 is "A1"
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
//expect(testEditor.getCursorPos()).toEqual(end);
|
||||
expect(testDoc.getRange(before, end)).toEqual("A1");
|
||||
});
|
||||
});
|
||||
@ -482,7 +537,7 @@ define(function (require, exports, module) {
|
||||
testDoc.replaceRange("A1.", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 0); // hint 0 is "propA"
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); // hint 0 is "propA"
|
||||
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
@ -499,7 +554,7 @@ define(function (require, exports, module) {
|
||||
testDoc.replaceRange("A1.prop", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 0); // hint 0 is "propA"
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); // hint 0 is "propA"
|
||||
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
@ -516,7 +571,7 @@ define(function (require, exports, module) {
|
||||
testDoc.replaceRange("A1.pro", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 0); // hint 0 is "propA"
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); // hint 0 is "propA"
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
expect(testDoc.getRange(start, end)).toEqual("A1.propA");
|
||||
@ -532,7 +587,7 @@ define(function (require, exports, module) {
|
||||
testDoc.replaceRange("A1.propB", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 0); // hint 0 is "propA"
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); // hint 0 is "propA"
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
expect(testDoc.getRange(start, end)).toEqual("A1.propA");
|
||||
@ -549,7 +604,7 @@ define(function (require, exports, module) {
|
||||
testDoc.replaceRange("(A1.prop)", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, 0); // hint 0 is "propA"
|
||||
selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); // hint 0 is "propA"
|
||||
|
||||
runs(function () {
|
||||
expect(testEditor.getCursorPos()).toEqual(end);
|
||||
@ -558,6 +613,58 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should list hints for string, as string assigned to 's', 's' assigned to 'r' and 'r' assigned to 't'", function () {
|
||||
var start = { line: 26, ch: 0 },
|
||||
middle = { line: 26, ch: 2 };
|
||||
|
||||
testDoc.replaceRange("t.", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
runs(function () {
|
||||
hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should list function type", function () {
|
||||
var start = { line: 36, ch: 0 },
|
||||
middle = { line: 36, ch: 5 };
|
||||
|
||||
testDoc.replaceRange("funD(", start, start);
|
||||
testEditor.setCursorPos(middle);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
runs(function () {
|
||||
hintsPresentExact(hintObj, ["funD(a: string, b: number) -> {x, y}"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should list exports from a requirejs module", function () {
|
||||
var start = { line: 40, ch: 21 };
|
||||
|
||||
testEditor.setCursorPos(start);
|
||||
var hintObj = expectHints(JSCodeHints.jsHintProvider);
|
||||
runs(function () {
|
||||
hintsPresentExact(hintObj, ["a", "b", "j"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should jump to function", function () {
|
||||
var start = { line: 43, ch: 0 };
|
||||
|
||||
testEditor.setCursorPos(start);
|
||||
runs(function () {
|
||||
editorJumped({line: 7, ch: 13});
|
||||
});
|
||||
});
|
||||
|
||||
it("should jump to var", function () {
|
||||
var start = { line: 44, ch: 10 };
|
||||
|
||||
testEditor.setCursorPos(start);
|
||||
runs(function () {
|
||||
editorJumped({line: 3, ch: 6});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -231,6 +231,7 @@ define({
|
||||
"CMD_QUICK_OPEN" : "Quick Open",
|
||||
"CMD_GOTO_LINE" : "Go to Line",
|
||||
"CMD_GOTO_DEFINITION" : "Go to Definition",
|
||||
"CMD_JUMPTO_DEFINITION" : "Jump to Definition",
|
||||
"CMD_JSLINT_FIRST_ERROR" : "Go to First JSLint Error",
|
||||
"CMD_TOGGLE_QUICK_EDIT" : "Quick Edit",
|
||||
"CMD_TOGGLE_QUICK_DOCS" : "Quick Docs",
|
||||
|
Loading…
Reference in New Issue
Block a user