[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;
|
this.input = input;
|
||||||
input.resume();
|
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('> ');
|
this.setPrompt('> ');
|
||||||
|
|
||||||
@ -247,68 +250,77 @@ Interface.prototype._insertString = function(c) {
|
|||||||
Interface.prototype._tabComplete = function() {
|
Interface.prototype._tabComplete = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var rv = this.completer(self.line.slice(0, self.cursor));
|
this.pause();
|
||||||
var completions = rv[0],
|
this.completer(self.line.slice(0, self.cursor), function(err, rv) {
|
||||||
completeOn = rv[1]; // the text that was completed
|
self.resume();
|
||||||
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 (err) {
|
||||||
if (group.length == 0) {
|
// XXX Log it somewhere?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var minRows = Math.ceil(group.length / maxColumns);
|
|
||||||
for (var row = 0; row < minRows; row++) {
|
var completions = rv[0],
|
||||||
for (var col = 0; col < maxColumns; col++) {
|
completeOn = rv[1]; // the text that was completed
|
||||||
var idx = row * maxColumns + col;
|
if (completions && completions.length) {
|
||||||
if (idx >= group.length) {
|
// Apply/show completions.
|
||||||
break;
|
if (completions.length === 1) {
|
||||||
}
|
self._insertString(completions[0].slice(completeOn.length));
|
||||||
var item = group[idx];
|
self._refreshLine();
|
||||||
self.output.write(item);
|
} else {
|
||||||
if (col < maxColumns - 1) {
|
self.output.write('\r\n');
|
||||||
for (var s = 0, itemLen = item.length; s < width - itemLen; s++) {
|
var width = completions.reduce(function(a, b) {
|
||||||
self.output.write(' ');
|
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');
|
||||||
}
|
}
|
||||||
self.output.write('\r\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
var group = [], c;
|
var group = [], c;
|
||||||
for (var i = 0, compLen = completions.length; i < compLen; i++) {
|
for (var i = 0, compLen = completions.length; i < compLen; i++) {
|
||||||
c = completions[i];
|
c = completions[i];
|
||||||
if (c === '') {
|
if (c === '') {
|
||||||
handleGroup(group);
|
handleGroup(group);
|
||||||
group = [];
|
group = [];
|
||||||
} else {
|
} else {
|
||||||
group.push(c);
|
group.push(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
handleGroup(group);
|
||||||
handleGroup(group);
|
|
||||||
|
|
||||||
// If there is a common prefix to all matches, then apply that
|
// If there is a common prefix to all matches, then apply that
|
||||||
// portion.
|
// portion.
|
||||||
var f = completions.filter(function(e) { if (e) return e; });
|
var f = completions.filter(function(e) { if (e) return e; });
|
||||||
var prefix = commonPrefix(f);
|
var prefix = commonPrefix(f);
|
||||||
if (prefix.length > completeOn.length) {
|
if (prefix.length > completeOn.length) {
|
||||||
self._insertString(prefix.slice(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;
|
exports.writer = util.inspect;
|
||||||
|
|
||||||
|
|
||||||
function REPLServer(prompt, stream) {
|
function REPLServer(prompt, stream, options) {
|
||||||
var self = this;
|
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.resetContext();
|
||||||
self.bufferedCommand = '';
|
self.bufferedCommand = '';
|
||||||
|
|
||||||
@ -82,8 +91,8 @@ function REPLServer(prompt, stream) {
|
|||||||
|
|
||||||
self.prompt = (prompt != undefined ? prompt : '> ');
|
self.prompt = (prompt != undefined ? prompt : '> ');
|
||||||
|
|
||||||
function complete(text) {
|
function complete(text, callback) {
|
||||||
return self.complete(text);
|
self.complete(text, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
var rli = rl.createInterface(self.inputStream, self.outputStream, complete);
|
var rli = rl.createInterface(self.inputStream, self.outputStream, complete);
|
||||||
@ -140,68 +149,85 @@ function REPLServer(prompt, stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!skipCatchall) {
|
if (!skipCatchall) {
|
||||||
// The catchall for errors
|
self.bufferedCommand += cmd + '\n';
|
||||||
try {
|
// This try is for determining if the command is complete, or should
|
||||||
self.bufferedCommand += cmd + '\n';
|
// continue onto the next line.
|
||||||
// This try is for determining if the command is complete, or should
|
// We try to evaluate both expressions e.g.
|
||||||
// continue onto the next line.
|
// '{ a : 1 }'
|
||||||
try {
|
// and statements e.g.
|
||||||
// We try to evaluate both expressions e.g.
|
// 'for (var i = 0; i < 10; i++) console.log(i);'
|
||||||
// '{ a : 1 }'
|
|
||||||
// and statements e.g.
|
|
||||||
// 'for (var i = 0; i < 10; i++) console.log(i);'
|
|
||||||
|
|
||||||
var ret, success = false;
|
// First we attempt to eval as expression with parens.
|
||||||
try {
|
// This catches '{a : 1}' properly.
|
||||||
// First we attempt to eval as expression with parens.
|
function tryParens() {
|
||||||
// This catches '{a : 1}' properly.
|
var success = false;
|
||||||
ret = vm.runInContext('(' + self.bufferedCommand + ')',
|
|
||||||
self.context,
|
self.eval('(' + self.bufferedCommand + ')',
|
||||||
'repl');
|
self.context,
|
||||||
if (typeof ret !== 'function') success = true;
|
'repl',
|
||||||
} catch (e) {
|
function(e, ret) {
|
||||||
if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) {
|
if (e) {
|
||||||
throw e;
|
if (!(e && e.constructor &&
|
||||||
|
e.constructor.name === 'SyntaxError')) {
|
||||||
|
finish(e);
|
||||||
} else {
|
} else {
|
||||||
success = false;
|
tryExpr();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (typeof ret !== 'function') {
|
||||||
// Now as statement without parens.
|
return tryExpr(ret);
|
||||||
ret = vm.runInContext(self.bufferedCommand, self.context, 'repl');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tryExpr();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now as statement without parens.
|
||||||
|
function tryExpr(ret) {
|
||||||
|
self.eval(self.bufferedCommand, self.context,
|
||||||
|
'repl', function(e, ret) {
|
||||||
|
|
||||||
if (ret !== undefined) {
|
if (ret !== undefined) {
|
||||||
self.context._ = ret;
|
self.context._ = ret;
|
||||||
self.outputStream.write(exports.writer(ret) + '\n');
|
self.outputStream.write(exports.writer(ret) + '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bufferedCommand = '';
|
self.bufferedCommand = '';
|
||||||
} catch (e) {
|
|
||||||
// instanceof doesn't work across context switches.
|
if (e) {
|
||||||
if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) {
|
// instanceof doesn't work across context switches.
|
||||||
throw e;
|
if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) {
|
||||||
// It could also be an error from JSON.parse
|
return finish(e);
|
||||||
} else if (e &&
|
// It could also be an error from JSON.parse
|
||||||
e.stack &&
|
} else if (e &&
|
||||||
e.stack.match(/^SyntaxError: Unexpected token .*\n/) &&
|
e.stack &&
|
||||||
e.stack.match(/\n at Object.parse \(native\)\n/)) {
|
e.stack.match(/^SyntaxError: Unexpected token .*\n/) &&
|
||||||
throw e;
|
e.stack.match(/\n at Object.parse \(native\)\n/)) {
|
||||||
|
return finish(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
finish(null, ret);
|
||||||
} catch (e) {
|
});
|
||||||
// On error: Print the error and clear the buffer
|
};
|
||||||
|
|
||||||
|
return tryParens();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(null);
|
||||||
|
function finish(e, ret) {
|
||||||
|
if (e) {
|
||||||
if (e.stack) {
|
if (e.stack) {
|
||||||
self.outputStream.write(e.stack + '\n');
|
self.outputStream.write(e.stack + '\n');
|
||||||
} else {
|
} else {
|
||||||
self.outputStream.write(e.toString() + '\n');
|
self.outputStream.write(e.toString() + '\n');
|
||||||
}
|
}
|
||||||
|
// On error: Print the error and clear the buffer
|
||||||
self.bufferedCommand = '';
|
self.bufferedCommand = '';
|
||||||
}
|
}
|
||||||
}
|
self.displayPrompt();
|
||||||
|
};
|
||||||
self.displayPrompt();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
rli.addListener('close', function() {
|
rli.addListener('close', function() {
|
||||||
@ -269,7 +295,7 @@ var simpleExpressionRE =
|
|||||||
//
|
//
|
||||||
// Warning: This eval's code like "foo.bar.baz", so it will run property
|
// Warning: This eval's code like "foo.bar.baz", so it will run property
|
||||||
// getter code.
|
// getter code.
|
||||||
REPLServer.prototype.complete = function(line) {
|
REPLServer.prototype.complete = function(line, callback) {
|
||||||
var completions;
|
var completions;
|
||||||
|
|
||||||
// list of completion lists, one for each inheritance "level"
|
// list of completion lists, one for each inheritance "level"
|
||||||
@ -406,84 +432,90 @@ REPLServer.prototype.complete = function(line) {
|
|||||||
'while', 'with', 'yield']);
|
'while', 'with', 'yield']);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
this.eval(expr, this.context, 'repl', function(e, obj) {
|
||||||
obj = vm.runInContext(expr, this.context, 'repl');
|
// if (e) console.log(e);
|
||||||
} catch (e) {
|
|
||||||
//console.log("completion eval error, expr='"+expr+"': "+e);
|
if (obj != null) {
|
||||||
}
|
if (typeof obj === 'object' || typeof obj === 'function') {
|
||||||
if (obj != null) {
|
memberGroups.push(Object.getOwnPropertyNames(obj));
|
||||||
if (typeof obj === 'object' || typeof obj === 'function') {
|
}
|
||||||
memberGroups.push(Object.getOwnPropertyNames(obj));
|
// works for non-objects
|
||||||
}
|
try {
|
||||||
// works for non-objects
|
var p = Object.getPrototypeOf(obj);
|
||||||
try {
|
var sentinel = 5;
|
||||||
var p = Object.getPrototypeOf(obj);
|
while (p !== null) {
|
||||||
var sentinel = 5;
|
memberGroups.push(Object.getOwnPropertyNames(p));
|
||||||
while (p !== null) {
|
p = Object.getPrototypeOf(p);
|
||||||
memberGroups.push(Object.getOwnPropertyNames(p));
|
// Circular refs possible? Let's guard against that.
|
||||||
p = Object.getPrototypeOf(p);
|
sentinel--;
|
||||||
// Circular refs possible? Let's guard against that.
|
if (sentinel <= 0) {
|
||||||
sentinel--;
|
break;
|
||||||
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) {
|
if (memberGroups.length) {
|
||||||
for (i = 0; i < memberGroups.length; i++) {
|
for (i = 0; i < memberGroups.length; i++) {
|
||||||
completionGroups.push(memberGroups[i].map(function(member) {
|
completionGroups.push(memberGroups[i].map(function(member) {
|
||||||
return expr + '.' + 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 reach this point - work like sync
|
||||||
if (completionGroups.length && filter) {
|
finish(null, ret);
|
||||||
var newCompletionGroups = [];
|
function finish(err, ret) {
|
||||||
for (i = 0; i < completionGroups.length; i++) {
|
if (err) throw err;
|
||||||
group = completionGroups[i].filter(function(elem) {
|
|
||||||
return elem.indexOf(filter) == 0;
|
|
||||||
});
|
|
||||||
if (group.length) {
|
|
||||||
newCompletionGroups.push(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completionGroups = newCompletionGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (completionGroups.length) {
|
// Filter, sort (within each group), uniq and merge the completion groups.
|
||||||
var uniq = {}; // unique completions across all groups
|
if (completionGroups.length && filter) {
|
||||||
completions = [];
|
var newCompletionGroups = [];
|
||||||
// Completion group 0 is the "closest" (least far up the inheritance chain)
|
for (i = 0; i < completionGroups.length; i++) {
|
||||||
// so we put its completions last: to be closest in the REPL.
|
group = completionGroups[i].filter(function(elem) {
|
||||||
for (i = completionGroups.length - 1; i >= 0; i--) {
|
return elem.indexOf(filter) == 0;
|
||||||
group = completionGroups[i];
|
});
|
||||||
group.sort();
|
if (group.length) {
|
||||||
for (var j = 0; j < group.length; j++) {
|
newCompletionGroups.push(group);
|
||||||
c = group[j];
|
|
||||||
if (!uniq.hasOwnProperty(c)) {
|
|
||||||
completions.push(c);
|
|
||||||
uniq[c] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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