diff --git a/lib/readline.js b/lib/readline.js index e03b3d276e..9cdc94e8d1 100644 --- a/lib/readline.js +++ b/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; - - 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(' '); + this.pause(); + this.completer(self.line.slice(0, self.cursor), function(err, rv) { + self.resume(); + + 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); + + // 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)); } - } - 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)); - } - self._refreshLine(); + self._refreshLine(); + } } - } + }); }; diff --git a/lib/repl.js b/lib/repl.js index 93b412ea64..cc9ffcea3a 100644 --- a/lib/repl.js +++ b/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);' - - 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; + 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);' + + // 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 (filter) { - filter = expr + '.' + filter; + 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); + // If reach this point - work like sync + finish(null, ret); + function finish(err, ret) { + if (err) throw err; + + // 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; } - 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; + 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(); } - completions.push(''); // separator btwn groups - } - while (completions.length && completions[completions.length - 1] === '') { - completions.pop(); } - } - return [completions || [], completeOn]; + callback(null, [completions || [], completeOn]); + } };