From e13ed4a8d0e612c2ec55fce4f3e390af91d1523c Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Thu, 8 Sep 2011 16:03:27 +0700 Subject: [PATCH] [repl, readline] refactor async completion and execution --- lib/readline.js | 4 +- lib/repl.js | 127 ++++++++++++++++++++---------------------------- 2 files changed, 54 insertions(+), 77 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index 7e20a08567..8781b90b93 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -250,8 +250,8 @@ Interface.prototype._insertString = function(c) { Interface.prototype._tabComplete = function() { var self = this; - this.pause(); - this.completer(self.line.slice(0, self.cursor), function(err, rv) { + self.pause(); + self.completer(self.line.slice(0, self.cursor), function(err, rv) { self.resume(); if (err) { diff --git a/lib/repl.js b/lib/repl.js index 82fe585289..597acbc6b7 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -159,79 +159,52 @@ function REPLServer(prompt, stream, eval) { // 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 { - tryExpr(e); - } - return; - } - - tryExpr(typeof ret === 'function', ret); - }); - }; - - // Now as statement without parens. - function tryExpr(e, ret) { - if (!e) return finish(null, ret); - - self.eval(self.bufferedCommand, self.context, - 'repl', function(e, ret) { - - 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); - } else { - finish(true); - return; - } - } + self.eval('(' + self.bufferedCommand + ')', + self.context, + 'repl', + function(e, ret) { + if (e) return finish(e); + + if (ret === 'function' || e) { + // Now as statement without parens. + self.eval(self.bufferedCommand, self.context, 'repl', finish); + } else { finish(null, ret); - }); - }; + } + }); - return tryParens(); + } else { + finish(null); } - finish(null); function finish(e, ret) { - if (e) { - if (e.stack) { - self.outputStream.write(e.stack + '\n'); - } else if (e === true) { - self.displayPrompt(); - return; - } else { - self.outputStream.write(e.toString() + '\n'); - } - // On error: Print the error and clear the buffer - self.bufferedCommand = ''; - } else { - self.bufferedCommand = ''; + // Convert error to string + e = e && (e.stack || e.toString()); + + // If error was SyntaxError and not JSON.parse error + if (e && e.match(/^SyntaxError/) && + !(e.match(/^SyntaxError: Unexpected token .*\n/) && + e.match(/\n at Object.parse \(native\)\n/))) { + // Start buffering data like that: + // { + // ... x: 1 + // ... } + self.displayPrompt(); + return; + } else if (e) { + self.outputStream.write(e + '\n'); } - if (ret !== undefined) { + // Clear buffer if no SyntaxErrors + self.bufferedCommand = ''; + + // If we got any output - print it (if no error) + if (!e && ret !== undefined) { self.context._ = ret; self.outputStream.write(exports.writer(ret) + '\n'); } + // Display prompt again self.displayPrompt(); }; }); @@ -319,6 +292,7 @@ REPLServer.prototype.complete = function(line, callback) { filter = match[1]; } + completionGroupsLoaded(); } else if (match = line.match(requireRE)) { // require('...') //TODO: suggest require.exts be exposed to be introspec registered @@ -384,6 +358,8 @@ REPLServer.prototype.complete = function(line, callback) { completionGroups.push(builtinLibs); } + completionGroupsLoaded(); + // 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 @@ -417,9 +393,12 @@ REPLServer.prototype.complete = function(line, callback) { // Resolve expr and get its completions. var obj, memberGroups = []; if (!expr) { + // If context is instance of vm.ScriptContext + // Get global vars synchronously if (this.context.constructor.name === 'Context') { completionGroups.push(Object.getOwnPropertyNames(this.context)); - next(); + addStandardGlobals(); + completionGroupsLoaded(); } else { this.eval('.scope', this.context, 'repl', function(err, globals) { if (Array.isArray(globals[0])) { @@ -427,17 +406,15 @@ REPLServer.prototype.complete = function(line, callback) { globals.forEach(function(group) { completionGroups.push(group); }); - finish(); } else { completionGroups.push(globals); - next(); + addStandardGlobals(); } + completionGroupsLoaded(); }); } - return; - - function next() { + function addStandardGlobals() { // Global object properties // (http://www.ecma-international.org/publications/standards/Ecma-262.htm) completionGroups.push(['NaN', 'Infinity', 'undefined', @@ -457,9 +434,8 @@ REPLServer.prototype.complete = function(line, callback) { 'throw', 'true', 'try', 'typeof', 'undefined', 'var', 'void', 'while', 'with', 'yield']); } - - finish(); } + } else { this.eval(expr, this.context, 'repl', function(e, obj) { // if (e) console.log(e); @@ -497,16 +473,17 @@ REPLServer.prototype.complete = function(line, callback) { } } - finish(); + completionGroupsLoaded(); }); - return; } + } else { + completionGroupsLoaded(); } } - // If reach this point - work like sync - finish(null); - function finish(err, ret) { + // Will be called when all completionGroups are in place + // Useful for async autocompletion + function completionGroupsLoaded(err) { if (err) throw err; // Filter, sort (within each group), uniq and merge the completion groups.