[repl, readline] async interface
Add async completion and execution interface for repl and readline
This commit is contained in:
parent
bd69afbc83
commit
42b8b77d9f
116
lib/readline.js
116
lib/readline.js
@ -51,7 +51,10 @@ function Interface(input, output, completer) {
|
||||
this.input = input;
|
||||
input.resume();
|
||||
|
||||
this.completer = completer;
|
||||
// Check arity, 2 - for async, 1 for sync
|
||||
this.completer = completer.length === 2 ? completer : function(v, callback) {
|
||||
callback(null, completer(v));
|
||||
};
|
||||
|
||||
this.setPrompt('> ');
|
||||
|
||||
@ -247,68 +250,77 @@ Interface.prototype._insertString = function(c) {
|
||||
Interface.prototype._tabComplete = function() {
|
||||
var self = this;
|
||||
|
||||
var rv = this.completer(self.line.slice(0, self.cursor));
|
||||
var completions = rv[0],
|
||||
completeOn = rv[1]; // the text that was completed
|
||||
if (completions && completions.length) {
|
||||
// Apply/show completions.
|
||||
if (completions.length === 1) {
|
||||
self._insertString(completions[0].slice(completeOn.length));
|
||||
self._refreshLine();
|
||||
} else {
|
||||
self.output.write('\r\n');
|
||||
var width = completions.reduce(function(a, b) {
|
||||
return a.length > b.length ? a : b;
|
||||
}).length + 2; // 2 space padding
|
||||
var maxColumns = Math.floor(this.columns / width) || 1;
|
||||
this.pause();
|
||||
this.completer(self.line.slice(0, self.cursor), function(err, rv) {
|
||||
self.resume();
|
||||
|
||||
function handleGroup(group) {
|
||||
if (group.length == 0) {
|
||||
return;
|
||||
}
|
||||
var minRows = Math.ceil(group.length / maxColumns);
|
||||
for (var row = 0; row < minRows; row++) {
|
||||
for (var col = 0; col < maxColumns; col++) {
|
||||
var idx = row * maxColumns + col;
|
||||
if (idx >= group.length) {
|
||||
break;
|
||||
}
|
||||
var item = group[idx];
|
||||
self.output.write(item);
|
||||
if (col < maxColumns - 1) {
|
||||
for (var s = 0, itemLen = item.length; s < width - itemLen; s++) {
|
||||
self.output.write(' ');
|
||||
if (err) {
|
||||
// XXX Log it somewhere?
|
||||
return;
|
||||
}
|
||||
|
||||
var completions = rv[0],
|
||||
completeOn = rv[1]; // the text that was completed
|
||||
if (completions && completions.length) {
|
||||
// Apply/show completions.
|
||||
if (completions.length === 1) {
|
||||
self._insertString(completions[0].slice(completeOn.length));
|
||||
self._refreshLine();
|
||||
} else {
|
||||
self.output.write('\r\n');
|
||||
var width = completions.reduce(function(a, b) {
|
||||
return a.length > b.length ? a : b;
|
||||
}).length + 2; // 2 space padding
|
||||
var maxColumns = Math.floor(this.columns / width) || 1;
|
||||
|
||||
function handleGroup(group) {
|
||||
if (group.length == 0) {
|
||||
return;
|
||||
}
|
||||
var minRows = Math.ceil(group.length / maxColumns);
|
||||
for (var row = 0; row < minRows; row++) {
|
||||
for (var col = 0; col < maxColumns; col++) {
|
||||
var idx = row * maxColumns + col;
|
||||
if (idx >= group.length) {
|
||||
break;
|
||||
}
|
||||
var item = group[idx];
|
||||
self.output.write(item);
|
||||
if (col < maxColumns - 1) {
|
||||
for (var s = 0, itemLen = item.length; s < width - itemLen; s++) {
|
||||
self.output.write(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
self.output.write('\r\n');
|
||||
}
|
||||
self.output.write('\r\n');
|
||||
}
|
||||
self.output.write('\r\n');
|
||||
}
|
||||
|
||||
var group = [], c;
|
||||
for (var i = 0, compLen = completions.length; i < compLen; i++) {
|
||||
c = completions[i];
|
||||
if (c === '') {
|
||||
handleGroup(group);
|
||||
group = [];
|
||||
} else {
|
||||
group.push(c);
|
||||
var group = [], c;
|
||||
for (var i = 0, compLen = completions.length; i < compLen; i++) {
|
||||
c = completions[i];
|
||||
if (c === '') {
|
||||
handleGroup(group);
|
||||
group = [];
|
||||
} else {
|
||||
group.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleGroup(group);
|
||||
handleGroup(group);
|
||||
|
||||
// If there is a common prefix to all matches, then apply that
|
||||
// portion.
|
||||
var f = completions.filter(function(e) { if (e) return e; });
|
||||
var prefix = commonPrefix(f);
|
||||
if (prefix.length > completeOn.length) {
|
||||
self._insertString(prefix.slice(completeOn.length));
|
||||
}
|
||||
// If there is a common prefix to all matches, then apply that
|
||||
// portion.
|
||||
var f = completions.filter(function(e) { if (e) return e; });
|
||||
var prefix = commonPrefix(f);
|
||||
if (prefix.length > completeOn.length) {
|
||||
self._insertString(prefix.slice(completeOn.length));
|
||||
}
|
||||
|
||||
self._refreshLine();
|
||||
self._refreshLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
252
lib/repl.js
252
lib/repl.js
@ -64,9 +64,18 @@ module.paths = require('module')._nodeModulePaths(module.filename);
|
||||
exports.writer = util.inspect;
|
||||
|
||||
|
||||
function REPLServer(prompt, stream) {
|
||||
function REPLServer(prompt, stream, options) {
|
||||
var self = this;
|
||||
|
||||
self.eval = options && options.eval || function(code, context, file, cb) {
|
||||
try {
|
||||
var err, result = vm.runInContext(code, context, file);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
cb(err, result);
|
||||
};
|
||||
|
||||
self.resetContext();
|
||||
self.bufferedCommand = '';
|
||||
|
||||
@ -82,8 +91,8 @@ function REPLServer(prompt, stream) {
|
||||
|
||||
self.prompt = (prompt != undefined ? prompt : '> ');
|
||||
|
||||
function complete(text) {
|
||||
return self.complete(text);
|
||||
function complete(text, callback) {
|
||||
self.complete(text, callback);
|
||||
}
|
||||
|
||||
var rli = rl.createInterface(self.inputStream, self.outputStream, complete);
|
||||
@ -140,68 +149,85 @@ function REPLServer(prompt, stream) {
|
||||
}
|
||||
|
||||
if (!skipCatchall) {
|
||||
// The catchall for errors
|
||||
try {
|
||||
self.bufferedCommand += cmd + '\n';
|
||||
// This try is for determining if the command is complete, or should
|
||||
// continue onto the next line.
|
||||
try {
|
||||
// We try to evaluate both expressions e.g.
|
||||
// '{ a : 1 }'
|
||||
// and statements e.g.
|
||||
// 'for (var i = 0; i < 10; i++) console.log(i);'
|
||||
self.bufferedCommand += cmd + '\n';
|
||||
// This try is for determining if the command is complete, or should
|
||||
// continue onto the next line.
|
||||
// We try to evaluate both expressions e.g.
|
||||
// '{ a : 1 }'
|
||||
// and statements e.g.
|
||||
// 'for (var i = 0; i < 10; i++) console.log(i);'
|
||||
|
||||
var ret, success = false;
|
||||
try {
|
||||
// First we attempt to eval as expression with parens.
|
||||
// This catches '{a : 1}' properly.
|
||||
ret = vm.runInContext('(' + self.bufferedCommand + ')',
|
||||
self.context,
|
||||
'repl');
|
||||
if (typeof ret !== 'function') success = true;
|
||||
} catch (e) {
|
||||
if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) {
|
||||
throw e;
|
||||
// First we attempt to eval as expression with parens.
|
||||
// This catches '{a : 1}' properly.
|
||||
function tryParens() {
|
||||
var success = false;
|
||||
|
||||
self.eval('(' + self.bufferedCommand + ')',
|
||||
self.context,
|
||||
'repl',
|
||||
function(e, ret) {
|
||||
if (e) {
|
||||
if (!(e && e.constructor &&
|
||||
e.constructor.name === 'SyntaxError')) {
|
||||
finish(e);
|
||||
} else {
|
||||
success = false;
|
||||
tryExpr();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// Now as statement without parens.
|
||||
ret = vm.runInContext(self.bufferedCommand, self.context, 'repl');
|
||||
if (typeof ret !== 'function') {
|
||||
return tryExpr(ret);
|
||||
}
|
||||
|
||||
tryExpr();
|
||||
});
|
||||
};
|
||||
|
||||
// Now as statement without parens.
|
||||
function tryExpr(ret) {
|
||||
self.eval(self.bufferedCommand, self.context,
|
||||
'repl', function(e, ret) {
|
||||
|
||||
if (ret !== undefined) {
|
||||
self.context._ = ret;
|
||||
self.outputStream.write(exports.writer(ret) + '\n');
|
||||
}
|
||||
|
||||
self.bufferedCommand = '';
|
||||
} catch (e) {
|
||||
// instanceof doesn't work across context switches.
|
||||
if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) {
|
||||
throw e;
|
||||
// It could also be an error from JSON.parse
|
||||
} else if (e &&
|
||||
e.stack &&
|
||||
e.stack.match(/^SyntaxError: Unexpected token .*\n/) &&
|
||||
e.stack.match(/\n at Object.parse \(native\)\n/)) {
|
||||
throw e;
|
||||
|
||||
if (e) {
|
||||
// instanceof doesn't work across context switches.
|
||||
if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) {
|
||||
return finish(e);
|
||||
// It could also be an error from JSON.parse
|
||||
} else if (e &&
|
||||
e.stack &&
|
||||
e.stack.match(/^SyntaxError: Unexpected token .*\n/) &&
|
||||
e.stack.match(/\n at Object.parse \(native\)\n/)) {
|
||||
return finish(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// On error: Print the error and clear the buffer
|
||||
finish(null, ret);
|
||||
});
|
||||
};
|
||||
|
||||
return tryParens();
|
||||
}
|
||||
|
||||
finish(null);
|
||||
function finish(e, ret) {
|
||||
if (e) {
|
||||
if (e.stack) {
|
||||
self.outputStream.write(e.stack + '\n');
|
||||
} else {
|
||||
self.outputStream.write(e.toString() + '\n');
|
||||
}
|
||||
// On error: Print the error and clear the buffer
|
||||
self.bufferedCommand = '';
|
||||
}
|
||||
}
|
||||
|
||||
self.displayPrompt();
|
||||
self.displayPrompt();
|
||||
};
|
||||
});
|
||||
|
||||
rli.addListener('close', function() {
|
||||
@ -269,7 +295,7 @@ var simpleExpressionRE =
|
||||
//
|
||||
// Warning: This eval's code like "foo.bar.baz", so it will run property
|
||||
// getter code.
|
||||
REPLServer.prototype.complete = function(line) {
|
||||
REPLServer.prototype.complete = function(line, callback) {
|
||||
var completions;
|
||||
|
||||
// list of completion lists, one for each inheritance "level"
|
||||
@ -406,84 +432,90 @@ REPLServer.prototype.complete = function(line) {
|
||||
'while', 'with', 'yield']);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
obj = vm.runInContext(expr, this.context, 'repl');
|
||||
} catch (e) {
|
||||
//console.log("completion eval error, expr='"+expr+"': "+e);
|
||||
}
|
||||
if (obj != null) {
|
||||
if (typeof obj === 'object' || typeof obj === 'function') {
|
||||
memberGroups.push(Object.getOwnPropertyNames(obj));
|
||||
}
|
||||
// works for non-objects
|
||||
try {
|
||||
var p = Object.getPrototypeOf(obj);
|
||||
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;
|
||||
}
|
||||
this.eval(expr, this.context, 'repl', function(e, obj) {
|
||||
// if (e) console.log(e);
|
||||
|
||||
if (obj != null) {
|
||||
if (typeof obj === 'object' || typeof obj === 'function') {
|
||||
memberGroups.push(Object.getOwnPropertyNames(obj));
|
||||
}
|
||||
// works for non-objects
|
||||
try {
|
||||
var p = Object.getPrototypeOf(obj);
|
||||
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);
|
||||
}
|
||||
} 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 (memberGroups.length) {
|
||||
for (i = 0; i < memberGroups.length; i++) {
|
||||
completionGroups.push(memberGroups[i].map(function(member) {
|
||||
return expr + '.' + member;
|
||||
}));
|
||||
}
|
||||
if (filter) {
|
||||
filter = expr + '.' + filter;
|
||||
}
|
||||
}
|
||||
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 reach this point - work like sync
|
||||
finish(null, ret);
|
||||
function finish(err, ret) {
|
||||
if (err) throw err;
|
||||
|
||||
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;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
completions.push(''); // separator btwn groups
|
||||
completionGroups = newCompletionGroups;
|
||||
}
|
||||
while (completions.length && completions[completions.length - 1] === '') {
|
||||
completions.pop();
|
||||
}
|
||||
}
|
||||
|
||||
return [completions || [], completeOn];
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, [completions || [], completeOn]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user