[repl, readline] refactor async completion and execution
authorFedor Indutny <fedor.indutny@gmail.com>
Thu, 8 Sep 2011 09:03:27 +0000 (16:03 +0700)
committerFedor Indutny <fedor.indutny@gmail.com>
Thu, 8 Sep 2011 19:06:07 +0000 (02:06 +0700)
lib/readline.js
lib/repl.js

index 7e20a08..8781b90 100644 (file)
@@ -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) {
index 82fe585..597acbc 100644 (file)
@@ -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('...<Tab>')
     //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.