|
|
@ -49,7 +49,9 @@ function REPLServer(prompt, stream) { |
|
|
|
self.stream = stream || process.openStdin(); |
|
|
|
self.prompt = prompt || "node> "; |
|
|
|
|
|
|
|
var rli = self.rli = rl.createInterface(self.stream); |
|
|
|
var rli = self.rli = rl.createInterface(self.stream, function (text) { |
|
|
|
return self.complete(text); |
|
|
|
}); |
|
|
|
rli.setPrompt(self.prompt); |
|
|
|
|
|
|
|
self.stream.addListener("data", function (chunk) { |
|
|
@ -135,6 +137,174 @@ REPLServer.prototype.displayPrompt = function () { |
|
|
|
REPLServer.prototype.readline = function (cmd) { |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Provide a list of completions for the given leading text. This is |
|
|
|
* given to the readline interface for handling tab completion. |
|
|
|
* |
|
|
|
* @param {line} The text (preceding the cursor) to complete |
|
|
|
* @returns {Array} Two elements: (1) an array of completions; and |
|
|
|
* (2) the leading text completed. |
|
|
|
* |
|
|
|
* Example: |
|
|
|
* complete('var foo = sys.') |
|
|
|
* -> [['sys.print', 'sys.debug', 'sys.log', 'sys.inspect', 'sys.pump'], |
|
|
|
* 'sys.' ] |
|
|
|
* |
|
|
|
* TODO: add warning about exec'ing code... property getters could be run |
|
|
|
*/ |
|
|
|
|
|
|
|
REPLServer.prototype.complete = function (line) { |
|
|
|
// TODO: special completion in `require` calls.
|
|
|
|
|
|
|
|
var completions, |
|
|
|
completionGroups = [], // list of completion lists, one for each inheritance "level"
|
|
|
|
completeOn, |
|
|
|
match, filter, i, j, group, c; |
|
|
|
|
|
|
|
// REPL comments (e.g. ".break").
|
|
|
|
var match = null; |
|
|
|
match = line.match(/^\s*(\.\w*)$/); |
|
|
|
if (match) { |
|
|
|
completionGroups.push(['.break', '.clear', '.exit', '.help']); |
|
|
|
completeOn = match[1]; |
|
|
|
if (match[1].length > 1) { |
|
|
|
filter = match[1]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Handle variable member lookup.
|
|
|
|
// We support simple chained expressions like the following (no function
|
|
|
|
// calls, etc.). That is for simplicity and also because we *eval* that
|
|
|
|
// leading expression so for safety (see WARNING above) don't want to
|
|
|
|
// eval function calls.
|
|
|
|
//
|
|
|
|
// foo.bar<|> # completions for 'foo' with filter 'bar'
|
|
|
|
// spam.eggs.<|> # completions for 'spam.eggs' with filter ''
|
|
|
|
// foo<|> # all scope vars with filter 'foo'
|
|
|
|
// foo.<|> # completions for 'foo' with filter ''
|
|
|
|
else if (line.length === 0 || line[line.length-1].match(/\w|\./)) { |
|
|
|
var simpleExpressionPat = /(([a-zA-Z_]\w*)\.)*([a-zA-Z_]\w*)\.?$/; |
|
|
|
match = simpleExpressionPat.exec(line); |
|
|
|
if (line.length === 0 || match) { |
|
|
|
var expr; |
|
|
|
completeOn = (match ? match[0] : ""); |
|
|
|
if (line.length === 0) { |
|
|
|
filter = ""; |
|
|
|
expr = ""; |
|
|
|
} else if (line[line.length-1] === '.') { |
|
|
|
filter = ""; |
|
|
|
expr = match[0].slice(0, match[0].length-1); |
|
|
|
} else { |
|
|
|
var bits = match[0].split('.'); |
|
|
|
filter = bits.pop(); |
|
|
|
expr = bits.join('.'); |
|
|
|
} |
|
|
|
//console.log("expression completion: completeOn='"+completeOn+"' expr='"+expr+"'");
|
|
|
|
|
|
|
|
// Resolve expr and get its completions.
|
|
|
|
var obj, memberGroups = []; |
|
|
|
if (!expr) { |
|
|
|
completionGroups.push(Object.getOwnPropertyNames(this.context)); |
|
|
|
// Global object properties
|
|
|
|
// (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
|
|
|
|
completionGroups.push(["NaN", "Infinity", "undefined", |
|
|
|
"eval", "parseInt", "parseFloat", "isNaN", "isFinite", "decodeURI", |
|
|
|
"decodeURIComponent", "encodeURI", "encodeURIComponent", |
|
|
|
"Object", "Function", "Array", "String", "Boolean", "Number", |
|
|
|
"Date", "RegExp", "Error", "EvalError", "RangeError", |
|
|
|
"ReferenceError", "SyntaxError", "TypeError", "URIError", |
|
|
|
"Math", "JSON"]); |
|
|
|
// Common keywords. Exclude for completion on the empty string, b/c
|
|
|
|
// they just get in the way.
|
|
|
|
if (filter) { |
|
|
|
completionGroups.push(["break", "case", "catch", "const", |
|
|
|
"continue", "debugger", "default", "delete", "do", "else", "export", |
|
|
|
"false", "finally", "for", "function", "if", "import", "in", |
|
|
|
"instanceof", "let", "new", "null", "return", "switch", "this", |
|
|
|
"throw", "true", "try", "typeof", "undefined", "var", "void", |
|
|
|
"while", "with", "yield"]) |
|
|
|
} |
|
|
|
} else { |
|
|
|
try { |
|
|
|
obj = evalcx(expr, this.context, "repl"); |
|
|
|
} catch (e) { |
|
|
|
//console.log("completion eval error, expr='"+expr+"': "+e);
|
|
|
|
} |
|
|
|
if (obj != null) { |
|
|
|
//TODO: The following, for example, misses "Object.isSealed". Is there
|
|
|
|
// a way to introspec those? Need to hardcode?
|
|
|
|
if (typeof obj === "object" || typeof obj === "function") { |
|
|
|
memberGroups.push(Object.getOwnPropertyNames(obj)); |
|
|
|
} |
|
|
|
var p = obj.constructor.prototype; // works for non-objects
|
|
|
|
try { |
|
|
|
var sentinel = 5; |
|
|
|
while (p !== null) { |
|
|
|
memberGroups.push(Object.getOwnPropertyNames(p)); |
|
|
|
p = Object.getPrototypeOf(p); |
|
|
|
// Circular refs possible? Let's guard against that.
|
|
|
|
sentinel--; |
|
|
|
if (sentinel <= 0) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
//console.log("completion error walking prototype chain:" + e);
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (memberGroups.length) { |
|
|
|
for (i = 0; i < memberGroups.length; i++) { |
|
|
|
completionGroups.push(memberGroups[i].map(function(member) { |
|
|
|
return expr + '.' + member; |
|
|
|
})); |
|
|
|
} |
|
|
|
if (filter) { |
|
|
|
filter = expr + '.' + filter; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Filter, sort (within each group), uniq and merge the completion groups.
|
|
|
|
if (completionGroups.length && filter) { |
|
|
|
var newCompletionGroups = []; |
|
|
|
for (i = 0; i < completionGroups.length; i++) { |
|
|
|
group = completionGroups[i].filter(function(elem) { |
|
|
|
return elem.indexOf(filter) == 0; |
|
|
|
}); |
|
|
|
if (group.length) { |
|
|
|
newCompletionGroups.push(group); |
|
|
|
} |
|
|
|
} |
|
|
|
completionGroups = newCompletionGroups; |
|
|
|
} |
|
|
|
if (completionGroups.length) { |
|
|
|
var uniq = {}; // unique completions across all groups
|
|
|
|
completions = []; |
|
|
|
// Completion group 0 is the "closest" (least far up the inheritance chain)
|
|
|
|
// so we put its completions last: to be closest in the REPL.
|
|
|
|
for (i = completionGroups.length - 1; i >= 0; i--) { |
|
|
|
group = completionGroups[i]; |
|
|
|
group.sort(); |
|
|
|
for (var j = 0; j < group.length; j++) { |
|
|
|
c = group[j]; |
|
|
|
if (!uniq.hasOwnProperty(c)) { |
|
|
|
completions.push(c); |
|
|
|
uniq[c] = true; |
|
|
|
} |
|
|
|
} |
|
|
|
completions.push(""); // separator btwn groups
|
|
|
|
} |
|
|
|
while (completions.length && completions[completions.length-1] === "") { |
|
|
|
completions.pop(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return [completions || [], completeOn]; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Used to parse and execute the Node REPL commands. |
|
|
|
* |
|
|
|