[debugger] restructurize code, eval control repl asynchronously
authorFedor Indutny <fedor.indutny@gmail.com>
Wed, 7 Sep 2011 14:29:01 +0000 (21:29 +0700)
committerFedor Indutny <fedor.indutny@gmail.com>
Thu, 8 Sep 2011 19:06:06 +0000 (02:06 +0700)
Move commands closer to each other, use .debugEval and .controlEval for
controlling repl output (no more incorrect 'debug>' prints).

lib/_debugger.js

index f79f42b..c7ba428 100644 (file)
@@ -589,6 +589,12 @@ var helpMessage = 'Commands: ' + commands.join(', ');
 function SourceUnderline(sourceText, position) {
   if (!sourceText) return;
 
+  var wrapper = require('module').wrapper[0];
+  if (sourceText.indexOf(wrapper) === 0) {
+    sourceText = sourceText.slice(wrapper.length);
+    position -= wrapper.length;
+  }
+
   // Create an underline with a caret pointing to the source position. If the
   // source contains a tab character the underline will have a tab character in
   // the same place otherwise the underline will have a space character.
@@ -623,7 +629,6 @@ function SourceInfo(body) {
   return result;
 }
 
-
 // This class is the repl-enabled debugger interface which is invoked on
 // "node debug"
 function Interface() {
@@ -634,14 +639,14 @@ function Interface() {
     self.killChild();
   });
 
-  this.stdin = process.openStdin();
-
   this.repl = new repl.REPLServer('debug> ');
+  this.repl.eval = this.controlEval.bind(this);
 
   // Lift all instance methods to repl context
   var proto = Interface.prototype,
       ignored = ['pause', 'resume', 'exitRepl', 'handleBreak',
-                 'requireConnection', 'killChild', 'trySpawn'];
+                 'requireConnection', 'killChild', 'trySpawn',
+                 'controlEval', 'debugEval'];
 
   for (var i in proto) {
     if (proto.hasOwnProperty(i) && ignored.indexOf(i) === -1) {
@@ -649,29 +654,37 @@ function Interface() {
     }
   }
 
-  this.quitting = false;
-  this.debugRepl = false;
+  this.waiting = null;
   this.paused = 0;
   this.context = this.repl.context;
 };
 
+
+// Stream control
+
+
 Interface.prototype.pause = function() {
   if (this.paused++ > 0) return false;
-  this.stdin.pause();
   this.repl.rli.pause();
+  process.stdin.pause();
 };
 
 Interface.prototype.resume = function() {
   if (this.paused === 0 || --this.paused !== 0) return false;
-  this.stdin.resume();
   this.repl.rli.resume();
-  process.stdout.write('\n');
   this.repl.displayPrompt();
+  process.stdin.resume();
+
+  if (this.waiting) {
+    this.waiting();
+    this.waiting = null;
+  }
 };
 
+
 Interface.prototype.handleBreak = function(r) {
   this.pause();
-  var result = '';
+  var result = '\n';
   if (r.breakpoints) {
     result += 'breakpoint';
     if (r.breakpoints.length > 1) {
@@ -698,11 +711,58 @@ Interface.prototype.handleBreak = function(r) {
   this.client.currentFrame = 0;
   this.client.currentScript = r.script.name;
 
-  process.stdout.write(result);
+  console.log(result);
   this.resume();
 };
 
 
+Interface.prototype.requireConnection = function() {
+  if (!this.client) throw Error('App isn\'t running... Try `run()` instead');
+};
+
+Interface.prototype.controlEval = function(code, context, filename, callback) {
+  try {
+    var result = vm.runInContext(code, context, filename);
+    if (this.paused === 0) return callback(null, result);
+    this.waiting = function() {
+      callback(null, result);
+    };
+  } catch (e) {
+    callback(e);
+  }
+};
+
+Interface.prototype.debugEval = function(code, context, filename, callback) {
+  var client = this.client;
+
+  if (code === '.scope') {
+    client.reqScopes(callback);
+    return;
+  }
+
+  client.reqEval(code, function(res) {
+    if (!res.success) {
+      if (res.message) {
+        if (/SyntaxError/.test(res.message)) {
+          callback(new SyntaxError(res.message));
+        } else {
+          callback(new Error(res.message));
+        }
+      } else {
+        callback(null);
+      }
+      return;
+    }
+
+    client.mirrorObject(res.body, function(mirror) {
+      callback(null, mirror);
+    });
+  });
+};
+
+
+// Commands
+
 function intChars(n) {
   // TODO dumb:
   if (n < 50) {
@@ -727,12 +787,16 @@ function leftPad(n) {
   return s;
 }
 
+
+// Print help message
 Interface.prototype.help = function() {
   this.pause();
   process.stdout.write(helpMessage);
   this.resume();
 };
 
+
+// Run script
 Interface.prototype.run = function() {
   if (this.child) {
     throw Error('App is already running... Try `restart()` instead');
@@ -741,6 +805,8 @@ Interface.prototype.run = function() {
   }
 };
 
+
+// Restart script
 Interface.prototype.restart = function() {
   if (!this.child) throw Error('App isn\'t running... Try `run()` instead');
 
@@ -754,11 +820,8 @@ Interface.prototype.restart = function() {
   }, 1000);
 };
 
-Interface.prototype.requireConnection = function() {
-  if (!this.client) throw Error('App isn\'t running... Try `run()` instead');
-};
-
 
+// Print version
 Interface.prototype.version = function() {
   this.requireConnection();
   var self = this;
@@ -770,6 +833,7 @@ Interface.prototype.version = function() {
   });
 };
 
+// List source code
 Interface.prototype.list = function() {
   this.requireConnection();
 
@@ -808,6 +872,7 @@ Interface.prototype.list = function() {
   });
 };
 
+// Print backtrace
 Interface.prototype.backtrace = function() {
   this.requireConnection();
 
@@ -834,12 +899,13 @@ Interface.prototype.backtrace = function() {
         text += '\n';
       }
 
-      process.stdout.write(text);
+      console.log(text);
     }
     self.resume();
   });
 };
 
+
 // argument full tells if it should display internal node scripts or not
 Interface.prototype.scripts = function(displayNatives) {
   this.requireConnection();
@@ -859,10 +925,12 @@ Interface.prototype.scripts = function(displayNatives) {
       }
     }
   }
-  process.stdout.write(text);
+  console.log(text);
   this.resume();
 };
 
+
+// Continue execution of script
 Interface.prototype.cont = function() {
   this.requireConnection();
   this.pause();
@@ -873,6 +941,8 @@ Interface.prototype.cont = function() {
   });
 };
 
+
+// Jump to next command
 Interface.prototype.next = function() {
   this.requireConnection();
 
@@ -884,6 +954,8 @@ Interface.prototype.next = function() {
   });
 };
 
+
+// Step in
 Interface.prototype.step = function() {
   this.requireConnection();
 
@@ -895,6 +967,8 @@ Interface.prototype.step = function() {
   });
 };
 
+
+// Step out
 Interface.prototype.out = function() {
   this.requireConnection();
 
@@ -906,6 +980,8 @@ Interface.prototype.out = function() {
   });
 };
 
+
+// Show breakpoints
 Interface.prototype.breakpoints = function() {
   this.requireConnection();
   this.pause();
@@ -920,53 +996,27 @@ Interface.prototype.breakpoints = function() {
   });
 };
 
+
+// Kill child process
 Interface.prototype.kill = function() {
   if (!this.child) return;
   this.killChild();
 };
 
+
+// Activate debug repl
 Interface.prototype.repl = function() {
-  if (!this.child) throw Error('App isn\'t running... Try `run()` instead');
+  this.requireConnection();
 
   var self = this;
 
-  this.debugRepl = true;
-
+  // Exit debug repl on Ctrl + C
   this.repl.rli.once('SIGINT', function() {
     self.exitRepl();
   });
 
-  var client = this.client;
-
-  // Save old eval
-  this.repl._eval = this.repl.eval;
-
   // Set new
-  this.repl.eval = function(code, context, filename, callback) {
-    if (code === '.scope') {
-      client.reqScopes(callback);
-      return;
-    }
-
-    client.reqEval(code, function(res) {
-      if (!res.success) {
-        if (res.message) {
-          if (/SyntaxError/.test(res.message)) {
-            callback(new SyntaxError(res.message));
-          } else {
-            callback(new Error(res.message));
-          }
-        } else {
-          callback(null);
-        }
-        return;
-      }
-
-      client.mirrorObject(res.body, function(mirror) {
-        callback(null, mirror);
-      });
-    });
-  };
+  this.repl.eval = this.debugEval.bind(this);
   this.repl.context = {};
 
   this.repl.prompt = '> ';
@@ -974,9 +1024,11 @@ Interface.prototype.repl = function() {
   this.repl.displayPrompt();
 };
 
+
+// Exit debug repl
 Interface.prototype.exitRepl = function() {
-  this.debugRepl = false;
-  this.repl.eval = this.repl._eval;
+  this.repl.eval = this.controlEval.bind(this);
+
   this.repl.context = this.context;
   this.repl.prompt = 'debug> ';
   this.repl.rli.setPrompt('debug> ');
@@ -1013,7 +1065,7 @@ Interface.prototype.trySpawn = function(cb) {
   var connectionAttempts = 0;
 
   client.once('ready', function() {
-    process.stdout.write(' ok');
+    process.stdout.write(' ok\n');
 
     // since we did debug-brk, we're hitting a break point immediately
     // continue before anything else.
@@ -1062,8 +1114,3 @@ Interface.prototype.trySpawn = function(cb) {
     attemptConnect();
   }, 50);
 };
-
-
-
-
-