[debugger] readline => repl
authorFedor Indutny <fedor.indutny@gmail.com>
Tue, 6 Sep 2011 20:08:48 +0000 (03:08 +0700)
committerFedor Indutny <fedor.indutny@gmail.com>
Thu, 8 Sep 2011 19:06:05 +0000 (02:06 +0700)
Started porting to high-level javascript API and repl.

lib/_debugger.js

index 4ae19d5..afee9b4 100644 (file)
@@ -20,7 +20,7 @@
 // USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 var net = require('net');
-var readline = require('readline');
+var repl = require('repl');
 var inherits = require('util').inherits;
 var spawn = require('child_process').spawn;
 
@@ -597,19 +597,11 @@ function SourceInfo(body) {
 }
 
 
-// This class is the readline-enabled debugger interface which is invoked on
+// This class is the repl-enabled debugger interface which is invoked on
 // "node debug"
 function Interface() {
-  var self = this;
-  var child;
-  var client;
-
-  function complete(line) {
-    return self.complete(line);
-  }
-
-  var term = readline.createInterface(process.stdin, process.stdout, complete);
-  this.term = term;
+  var self = this,
+      child;
 
   process.on('exit', function() {
     self.killChild();
@@ -617,104 +609,45 @@ function Interface() {
 
   this.stdin = process.openStdin();
 
-  term.setPrompt('debug> ');
-  term.prompt();
-
-  this.quitting = false;
-
-  process.on('SIGINT', function() {
-    self.handleSIGINT();
-  });
-
-  term.on('SIGINT', function() {
-    self.handleSIGINT();
-  });
-
-  term.on('attemptClose', function() {
-    self.tryQuit();
-  });
-
-  term.on('line', function(cmd) {
-    // trim whitespace
-    cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, '');
-
-    if (cmd.length) {
-      self._lastCommand = cmd;
-      self.handleCommand(cmd);
-    } else {
-      self.handleCommand(self._lastCommand);
-    }
-  });
-}
-
+  this.repl = repl.start('debug> ');
 
-Interface.prototype.complete = function(line) {
-  // Match me with a command.
-  var matches = [];
-  // Remove leading whitespace
-  line = line.replace(/^\s*/, '');
+  // Lift all instance methods to repl context
+  var proto = Interface.prototype,
+      ignored = ['pause', 'resume', 'handleSIGINT'];
 
-  for (var i = 0; i < commands.length; i++) {
-    if (commands[i].indexOf(line) === 0) {
-      matches.push(commands[i]);
+  for (var i in proto) {
+    if (proto.hasOwnProperty(i) && ignored.indexOf(i) === -1) {
+      this.repl.context[i] = proto[i].bind(this);
     }
   }
 
-  return [matches, line];
-};
-
-
-Interface.prototype.handleSIGINT = function() {
-  if (this.paused) {
-    this.child.kill('SIGINT');
-  } else {
-    this.tryQuit();
-  }
-};
-
-
-Interface.prototype.quit = function() {
-  if (this.quitting) return;
-  this.quitting = true;
-  this.killChild();
-  this.term.close();
-  process.exit(0);
-};
-
-
-Interface.prototype.tryQuit = function() {
-  var self = this;
+  this.quitting = false;
+  this.paused = 0;
 
-  if (self.child) {
-    self.quitQuestion(function(yes) {
-      if (yes) {
-        self.quit();
-      } else {
-        self.term.prompt();
-      }
-    });
-  } else {
-    self.quit();
-  }
+  process.on('SIGINT', function() {
+    self.handleSIGINT();
+  });
 };
 
-
 Interface.prototype.pause = function() {
-  this.paused = true;
+  if (this.paused++ > 0) return false;
   this.stdin.pause();
-  this.term.pause();
+  this.repl.rli.pause();
 };
 
-
 Interface.prototype.resume = function() {
-  if (!this.paused) return false;
-  this.paused = false;
+  if (this.paused === 0 || --this.paused !== 0) return false;
   this.stdin.resume();
-  this.term.resume();
-  this.term.prompt();
-  return true;
+  this.repl.rli.resume();
+  process.stdout.write('\n');
+  this.repl.displayPrompt();
 };
 
+Interface.prototype.handleSIGINT = function() {
+  this.child.kill('SIGINT');
+};
+
+
 
 Interface.prototype.handleBreak = function(r) {
   var result = '';
@@ -745,8 +678,6 @@ Interface.prototype.handleBreak = function(r) {
   this.client.currentScript = r.script.name;
 
   console.log(result);
-
-  if (!this.resume()) this.term.prompt();
 };
 
 
@@ -774,6 +705,46 @@ function leftPad(n) {
   return s;
 }
 
+Interface.prototype.help = function() {
+  this.pause();
+  process.stdout.write(helpMessage);
+  this.resume();
+};
+
+Interface.prototype.run = function() {
+  if (this.child) {
+    throw Error('App is already running... Try `restart()` instead');
+  } else {
+    this.trySpawn();
+  }
+};
+
+Interface.prototype.restart = function() {
+  if (!this.child) throw Error('App isn\'t running... Try `run()` instead');
+
+  var self = this;
+
+  this.killChild();
+
+  // XXX need to wait a little bit for the restart to work?
+  setTimeout(function() {
+    self.trySpawn();
+  }, 1000);
+};
+
+Interface.prototype.version = function() {
+  if (!this.child) throw Error('App isn\'t running... Try `run()` instead');
+
+  var self = this;
+
+  this.pause();
+  this.client.reqVersion(function(v) {
+    process.stdout.write(v);
+    self.resume();
+  });
+};
+
+
 
 Interface.prototype.handleCommand = function(cmd) {
   var self = this;
@@ -786,29 +757,9 @@ Interface.prototype.handleCommand = function(cmd) {
     self.tryQuit();
 
   } else if (/^r(un)?/.test(cmd)) {
-    self._lastCommand = null;
-    if (self.child) {
-      self.restartQuestion(function(yes) {
-        if (!yes) {
-          self._lastCommand = null;
-          term.prompt();
-        } else {
-          console.log('restarting...');
-          self.killChild();
-          // XXX need to wait a little bit for the restart to work?
-          setTimeout(function() {
-            self.trySpawn();
-          }, 1000);
-        }
-      });
-    } else {
-      self.trySpawn();
-    }
-
+    // DONE
   } else if (/^help/.test(cmd)) {
-    console.log(helpMessage);
-    term.prompt();
-
+    // DONE
   } else if ('version' == cmd) {
     if (!client) {
       self.printNotConnected();
@@ -816,7 +767,6 @@ Interface.prototype.handleCommand = function(cmd) {
     }
     client.reqVersion(function(v) {
       console.log(v);
-      term.prompt();
     });
 
   } else if (/info +breakpoints/.test(cmd)) {
@@ -826,7 +776,6 @@ Interface.prototype.handleCommand = function(cmd) {
     }
     client.listbreakpoints(function(res) {
       console.log(res);
-      term.prompt();
     });
 
 
@@ -864,7 +813,6 @@ Interface.prototype.handleCommand = function(cmd) {
           console.log(leftPad(lineno) + ' ' + lines[i]);
         }
       }
-      term.prompt();
     });
 
   } else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) {
@@ -894,7 +842,6 @@ Interface.prototype.handleCommand = function(cmd) {
 
         console.log(text);
       }
-      term.prompt();
     });
 
   } else if (cmd == 'scripts' || cmd == 'scripts full') {
@@ -903,7 +850,6 @@ Interface.prototype.handleCommand = function(cmd) {
       return;
     }
     self.printScripts(cmd.indexOf('full') > 0);
-    term.prompt();
 
   } else if (/^c(ontinue)?/.test(cmd)) {
     if (!client) {
@@ -931,7 +877,6 @@ Interface.prototype.handleCommand = function(cmd) {
         }
       });
     } else {
-      self.term.prompt();
     }
 
   } else if (/^next/.test(cmd) || /^n/.test(cmd)) {
@@ -960,19 +905,16 @@ Interface.prototype.handleCommand = function(cmd) {
     var i = cmd.indexOf(' ');
     if (i < 0) {
       console.log('print [expression]');
-      term.prompt();
     } else {
       cmd = cmd.slice(i);
       client.reqEval(cmd, function(res) {
         if (!res.success) {
           console.log(res.message);
-          term.prompt();
           return;
         }
 
         client.mirrorObject(res.body, function(mirror) {
           console.log(mirror);
-          term.prompt();
         });
       });
     }
@@ -982,45 +924,11 @@ Interface.prototype.handleCommand = function(cmd) {
       // If it's not all white-space print this error message.
       console.log('Unknown command "%s". Try "help"', cmd);
     }
-    term.prompt();
   }
 };
 
 
 
-Interface.prototype.yesNoQuestion = function(prompt, cb) {
-  var self = this;
-  self.resume();
-  this.term.question(prompt, function(answer) {
-    if (/^y(es)?$/i.test(answer)) {
-      cb(true);
-    } else if (/^n(o)?$/i.test(answer)) {
-      cb(false);
-    } else {
-      console.log('Please answer y or n.');
-      self.yesNoQuestion(prompt, cb);
-    }
-  });
-};
-
-
-Interface.prototype.restartQuestion = function(cb) {
-  this.yesNoQuestion('The program being debugged has been started already.\n' +
-                     'Start it from the beginning? (y or n) ', cb);
-};
-
-
-Interface.prototype.killQuestion = function(cb) {
-  this.yesNoQuestion('Kill the program being debugged? (y or n) ', cb);
-};
-
-
-Interface.prototype.quitQuestion = function(cb) {
-  this.yesNoQuestion('A debugging session is active. Quit anyway? (y or n) ',
-                     cb);
-};
-
-
 Interface.prototype.killChild = function() {
   if (this.child) {
     this.child.kill();
@@ -1050,26 +958,25 @@ Interface.prototype.trySpawn = function(cb) {
   var connectionAttempts = 0;
 
   client.once('ready', function() {
-    process.stdout.write(' ok\r\n');
+    process.stdout.write(' ok');
 
     // since we did debug-brk, we're hitting a break point immediately
     // continue before anything else.
     client.reqContinue(function() {
+      self.resume();
       if (cb) cb();
     });
 
     client.on('close', function() {
-      console.log('\nprogram terminated');
+      console.log('program terminated');
       self.client = null;
       self.killChild();
-      if (!self.quitting) self.term.prompt();
     });
   });
 
   client.on('unhandledResponse', function(res) {
     console.log('\r\nunhandled res:');
     console.log(res);
-    self.term.prompt();
   });
 
   client.on('break', function(res) {
@@ -1098,12 +1005,6 @@ Interface.prototype.trySpawn = function(cb) {
 };
 
 
-Interface.prototype.printNotConnected = function() {
-  console.log("Program not running. Try 'run'.");
-  this.term.prompt();
-};
-
-
 // argument full tells if it should display internal node scripts or not
 Interface.prototype.printScripts = function(displayNatives) {
   var client = this.client;