text: // Copyright Joyent, Inc. and other Node contributors.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a
  // copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to permit
  // persons to whom the Software is furnished to do so, subject to the
  // following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  var util = require('util'),
      path = require('path'),
      net = require('net'),
      vm = require('vm'),
      repl = require('repl'),
      inherits = util.inherits,
      assert = require('assert'),
      spawn = require('child_process').spawn;
  
  exports.start = function(argv, stdin, stdout) {
    argv || (argv = process.argv.slice(2));
  
    if (argv.length < 1) {
      console.error('Usage: node debug script.js');
      process.exit(1);
    }
  
    // Setup input/output streams
    stdin = stdin || process.stdin;
    stdout = stdout || process.stdout;
  
    var args = ['--debug-brk'].concat(argv),
        interface_ = new Interface(stdin, stdout, args);
  
    stdin.resume();
  
    process.on('uncaughtException', function(e) {
      console.error("There was an internal error in Node's debugger. " +
          'Please report this bug.');
      console.error(e.message);
      console.error(e.stack);
      if (interface_.child) interface_.child.kill();
      process.exit(1);
    });
  };
  
  exports.port = 5858;
  
  
  //
  // Parser/Serializer for V8 debugger protocol
  // http://code.google.com/p/v8/wiki/DebuggerProtocol
  //
  // Usage:
  //    p = new Protocol();
  //
  //    p.onResponse = function(res) {
  //      // do stuff with response from V8
  //    };
  //
  //    socket.setEncoding('utf8');
  //    socket.on('data', function(s) {
  //      // Pass strings into the protocol
  //      p.execute(s);
  //    });
  //
  //
  function Protocol() {
    this._newRes();
  }
  exports.Protocol = Protocol;
  
  
  Protocol.prototype._newRes = function(raw) {
    this.res = { raw: raw || '', headers: {} };
    this.state = 'headers';
    this.reqSeq = 1;
    this.execute('');
  };
  
  
  Protocol.prototype.execute = function(d) {
    var res = this.res;
    res.raw += d;
  
    switch (this.state) {
      case 'headers':
        var endHeaderIndex = res.raw.indexOf('\r\n\r\n');
  
        if (endHeaderIndex < 0) break;
  
        var rawHeader = res.raw.slice(0, endHeaderIndex);
        var endHeaderByteIndex = Buffer.byteLength(rawHeader, 'utf8');
        var lines = rawHeader.split('\r\n');
        for (var i = 0; i < lines.length; i++) {
          var kv = lines[i].split(/: +/);
          res.headers[kv[0]] = kv[1];
        }
  
        this.contentLength = +res.headers['Content-Length'];
        this.bodyStartByteIndex = endHeaderByteIndex + 4;
  
        this.state = 'body';
  
        var len = Buffer.byteLength(res.raw, 'utf8');
        if (len - this.bodyStartByteIndex < this.contentLength) {
          break;
        }
        // pass thru
      case 'body':
        var resRawByteLength = Buffer.byteLength(res.raw, 'utf8');
  
        if (resRawByteLength - this.bodyStartByteIndex >= this.contentLength) {
          var buf = new Buffer(resRawByteLength);
          buf.write(res.raw, 0, resRawByteLength, 'utf8');
          res.body =
              buf.slice(this.bodyStartByteIndex,
                        this.bodyStartByteIndex +
                        this.contentLength).toString('utf8');
          // JSON parse body?
          res.body = res.body.length ? JSON.parse(res.body) : {};
  
          // Done!
          this.onResponse(res);
  
          this._newRes(buf.slice(this.bodyStartByteIndex +
                                 this.contentLength).toString('utf8'));
        }
        break;
  
      default:
        throw new Error('Unknown state');
        break;
    }
  };
  
  
  Protocol.prototype.serialize = function(req) {
    req.type = 'request';
    req.seq = this.reqSeq++;
    var json = JSON.stringify(req);
    return 'Content-Length: ' + Buffer.byteLength(json, 'utf8') +
           '\r\n\r\n' + json;
  };
  
  
  var NO_FRAME = -1;
  
  function Client() {
    net.Stream.call(this);
    var protocol = this.protocol = new Protocol(this);
    this._reqCallbacks = [];
    var socket = this;
  
    this.currentFrame = NO_FRAME;
    this.currentSourceLine = -1;
    this.handles = {};
    this.scripts = {};
    this.breakpoints = [];
  
    // Note that 'Protocol' requires strings instead of Buffers.
    socket.setEncoding('utf8');
    socket.on('data', function(d) {
      protocol.execute(d);
    });
  
    protocol.onResponse = this._onResponse.bind(this);
  }
  inherits(Client, net.Stream);
  exports.Client = Client;
  
  
  Client.prototype._addHandle = function(desc) {
    if (!util.isObject(desc) || !util.isNumber(desc.handle)) {
      return;
    }
  
    this.handles[desc.handle] = desc;
  
    if (desc.type == 'script') {
      this._addScript(desc);
    }
  };
  
  
  var natives = process.binding('natives');
  
  
  Client.prototype._addScript = function(desc) {
    this.scripts[desc.id] = desc;
    if (desc.name) {
      desc.isNative = (desc.name.replace('.js', '') in natives) ||
                      desc.name == 'node.js';
    }
  };
  
  
  Client.prototype._removeScript = function(desc) {
    this.scripts[desc.id] = undefined;
  };
  
  
  Client.prototype._onResponse = function(res) {
    var cb,
        index = -1;
  
    this._reqCallbacks.some(function(fn, i) {
      if (fn.request_seq == res.body.request_seq) {
        cb = fn;
        index = i;
        return true;
      }
    });
  
    var self = this;
    var handled = false;
  
    if (res.headers.Type == 'connect') {
      // Request a list of scripts for our own storage.
      self.reqScripts();
      self.emit('ready');
      handled = true;
  
    } else if (res.body && res.body.event == 'break') {
      this.emit('break', res.body);
      handled = true;
  
    } else if (res.body && res.body.event == 'exception') {
      this.emit('exception', res.body);
      handled = true;
  
    } else if (res.body && res.body.event == 'afterCompile') {
      this._addHandle(res.body.body.script);
      handled = true;
  
    } else if (res.body && res.body.event == 'scriptCollected') {
      // ???
      this._removeScript(res.body.body.script);
      handled = true;
  
    }
  
    if (cb) {
      this._reqCallbacks.splice(index, 1);
      handled = true;
  
      var err = res.success === false && (res.message || true) ||
                res.body.success === false && (res.body.message || true);
      cb(err, res.body && res.body.body || res.body, res);
    }
  
    if (!handled) this.emit('unhandledResponse', res.body);
  };
  
  
  Client.prototype.req = function(req, cb) {
    this.write(this.protocol.serialize(req));
    cb.request_seq = req.seq;
    this._reqCallbacks.push(cb);
  };
  
  
  Client.prototype.reqVersion = function(cb) {
    cb = cb || function() {};
    this.req({ command: 'version' } , function(err, body, res) {
      if (err) return cb(err);
      cb(null, res.body.body.V8Version, res.body.running);
    });
  };
  
  
  Client.prototype.reqLookup = function(refs, cb) {
    var self = this;
  
    // TODO: We have a cache of handle's we've already seen in this.handles
    // This can be used if we're careful.
    var req = {
      command: 'lookup',
      arguments: {
        handles: refs
      }
    };
  
    cb = cb || function() {};
    this.req(req, function(err, res) {
      if (err) return cb(err);
      for (var ref in res) {
        if (util.isObject(res[ref])) {
          self._addHandle(res[ref]);
        }
      }
  
      cb(null, res);
    });
  };
  
  Client.prototype.reqScopes = function(cb) {
    var self = this,
        req = {
          command: 'scopes',
          arguments: {}
        };
  
    cb = cb || function() {};
    this.req(req, function(err, res) {
      if (err) return cb(err);
      var refs = res.scopes.map(function(scope) {
        return scope.object.ref;
      });
  
      self.reqLookup(refs, function(err, res) {
        if (err) return cb(err);
  
        var globals = Object.keys(res).map(function(key) {
          return res[key].properties.map(function(prop) {
            return prop.name;
          });
        });
  
        cb(null, globals.reverse());
      });
    });
  };
  
  // This is like reqEval, except it will look up the expression in each of the
  // scopes associated with the current frame.
  Client.prototype.reqEval = function(expression, cb) {
    var self = this;
  
    if (this.currentFrame == NO_FRAME) {
      // Only need to eval in global scope.
      this.reqFrameEval(expression, NO_FRAME, cb);
      return;
    }
  
    cb = cb || function() {};
    // Otherwise we need to get the current frame to see which scopes it has.
    this.reqBacktrace(function(err, bt) {
      if (err || !bt.frames) {
        // ??
        return cb(null, {});
      }
  
      var frame = bt.frames[self.currentFrame];
  
      var evalFrames = frame.scopes.map(function(s) {
        if (!s) return;
        var x = bt.frames[s.index];
        if (!x) return;
        return x.index;
      });
  
      self._reqFramesEval(expression, evalFrames, cb);
    });
  };
  
  
  // Finds the first scope in the array in which the expression evals.
  Client.prototype._reqFramesEval = function(expression, evalFrames, cb) {
    if (evalFrames.length == 0) {
      // Just eval in global scope.
      this.reqFrameEval(expression, NO_FRAME, cb);
      return;
    }
  
    var self = this;
    var i = evalFrames.shift();
  
    cb = cb || function() {};
    this.reqFrameEval(expression, i, function(err, res) {
      if (!err) return cb(null, res);
      self._reqFramesEval(expression, evalFrames, cb);
    });
  };
  
  
  Client.prototype.reqFrameEval = function(expression, frame, cb) {
    var self = this;
    var req = {
      command: 'evaluate',
      arguments: { expression: expression }
    };
  
    if (frame == NO_FRAME) {
      req.arguments.global = true;
    } else {
      req.arguments.frame = frame;
    }
  
    cb = cb || function() {};
    this.req(req, function(err, res) {
      if (!err) self._addHandle(res);
      cb(err, res);
    });
  };
  
  
  // reqBacktrace(cb)
  // TODO: from, to, bottom
  Client.prototype.reqBacktrace = function(cb) {
    this.req({ command: 'backtrace', arguments: { inlineRefs: true } } , cb);
  };
  
  
  // reqSetExceptionBreak(type, cb)
  // TODO: from, to, bottom
  Client.prototype.reqSetExceptionBreak = function(type, cb) {
    this.req({
      command: 'setexceptionbreak',
      arguments: { type: type, enabled: true }
    }, cb);
  };
  
  
  // Returns an array of objects like this:
  //
  //   { handle: 11,
  //     type: 'script',
  //     name: 'node.js',
  //     id: 14,
  //     lineOffset: 0,
  //     columnOffset: 0,
  //     lineCount: 562,
  //     sourceStart: '(function(process) {\n\n  ',
  //     sourceLength: 15939,
  //     scriptType: 2,
  //     compilationType: 0,
  //     context: { ref: 10 },
  //     text: 'node.js (lines: 562)' }
  //
  Client.prototype.reqScripts = function(cb) {
    var self = this;
    cb = cb || function() {};
  
    this.req({ command: 'scripts' } , function(err, res) {
      if (err) return cb(err);
  
      for (var i = 0; i < res.length; i++) {
        self._addHandle(res[i]);
      }
      cb(null);
    });
  };
  
  
  Client.prototype.reqContinue = function(cb) {
    this.currentFrame = NO_FRAME;
    this.req({ command: 'continue' }, cb);
  };
  
  Client.prototype.listbreakpoints = function(cb) {
    this.req({ command: 'listbreakpoints' }, cb);
  };
  
  Client.prototype.setBreakpoint = function(req, cb) {
    req = {
      command: 'setbreakpoint',
      arguments: req
    };
  
    this.req(req, cb);
  };
  
  Client.prototype.clearBreakpoint = function(req, cb) {
    var req = {
      command: 'clearbreakpoint',
      arguments: req
    };
  
    this.req(req, cb);
  };
  
  Client.prototype.reqSource = function(from, to, cb) {
    var req = {
      command: 'source',
      fromLine: from,
      toLine: to
    };
  
    this.req(req, cb);
  };
  
  
  // client.next(1, cb);
  Client.prototype.step = function(action, count, cb) {
    var req = {
      command: 'continue',
      arguments: { stepaction: action, stepcount: count }
    };
  
    this.currentFrame = NO_FRAME;
    this.req(req, cb);
  };
  
  
  Client.prototype.mirrorObject = function(handle, depth, cb) {
    var self = this;
  
    var val;
  
    if (handle.type === 'object') {
      // The handle looks something like this:
      // { handle: 8,
      //   type: 'object',
      //   className: 'Object',
      //   constructorFunction: { ref: 9 },
      //   protoObject: { ref: 4 },
      //   prototypeObject: { ref: 2 },
      //   properties: [ { name: 'hello', propertyType: 1, ref: 10 } ],
      //   text: '#<an Object>' }
  
      // For now ignore the className and constructor and prototype.
      // TJ's method of object inspection would probably be good for this:
      // https://groups.google.com/forum/?pli=1#!topic/nodejs-dev/4gkWBOimiOg
  
      var propertyRefs = handle.properties.map(function(p) {
        return p.ref;
      });
  
      cb = cb || function() {};
      this.reqLookup(propertyRefs, function(err, res) {
        if (err) {
          console.error('problem with reqLookup');
          cb(null, handle);
          return;
        }
  
        var mirror,
            waiting = 1;
  
        if (handle.className == 'Array') {
          mirror = [];
        } else if (handle.className == 'Date') {
          mirror = new Date(handle.value);
        } else {
          mirror = {};
        }
  
  
        var keyValues = [];
        handle.properties.forEach(function(prop, i) {
          var value = res[prop.ref];
          var mirrorValue;
          if (value) {
            mirrorValue = value.value ? value.value : value.text;
          } else {
            mirrorValue = '[?]';
          }
  
  
          if (util.isArray(mirror) && !util.isNumber(prop.name)) {
            // Skip the 'length' property.
            return;
          }
  
          keyValues[i] = {
            name: prop.name,
            value: mirrorValue
          };
          if (value && value.handle && depth > 0) {
            waiting++;
            self.mirrorObject(value, depth - 1, function(err, result) {
              if (!err) keyValues[i].value = result;
              waitForOthers();
            });
          }
        });
  
        waitForOthers();
        function waitForOthers() {
          if (--waiting === 0 && cb) {
            keyValues.forEach(function(kv) {
              mirror[kv.name] = kv.value;
            });
            cb(null, mirror);
          }
        };
      });
      return;
    } else if (handle.type === 'function') {
      val = function() {};
    } else if (handle.type === 'null') {
      val = null;
    } else if (!util.isUndefined(handle.value)) {
      val = handle.value;
    } else if (handle.type === 'undefined') {
      val = undefined;
    } else {
      val = handle;
    }
    process.nextTick(function() {
      cb(null, val);
    });
  };
  
  
  Client.prototype.fullTrace = function(cb) {
    var self = this;
  
    cb = cb || function() {};
    this.reqBacktrace(function(err, trace) {
      if (err) return cb(err);
      if (trace.totalFrames <= 0) return cb(Error('No frames'));
  
      var refs = [];
  
      for (var i = 0; i < trace.frames.length; i++) {
        var frame = trace.frames[i];
        // looks like this:
        // { type: 'frame',
        //   index: 0,
        //   receiver: { ref: 1 },
        //   func: { ref: 0 },
        //   script: { ref: 7 },
        //   constructCall: false,
        //   atReturn: false,
        //   debuggerFrame: false,
        //   arguments: [],
        //   locals: [],
        //   position: 160,
        //   line: 7,
        //   column: 2,
        //   sourceLineText: '  debugger;',
        //   scopes: [ { type: 1, index: 0 }, { type: 0, index: 1 } ],
        //   text: '#00 blah() /home/ryan/projects/node/test-debug.js l...' }
        refs.push(frame.script.ref);
        refs.push(frame.func.ref);
        refs.push(frame.receiver.ref);
      }
  
      self.reqLookup(refs, function(err, res) {
        if (err) return cb(err);
  
        for (var i = 0; i < trace.frames.length; i++) {
          var frame = trace.frames[i];
          frame.script = res[frame.script.ref];
          frame.func = res[frame.func.ref];
          frame.receiver = res[frame.receiver.ref];
        }
  
        cb(null, trace);
      });
    });
  };
  
  
  
  
  
  
  var commands = [
    [
      'run (r)',
      'cont (c)',
      'next (n)',
      'step (s)',
      'out (o)',
      'backtrace (bt)',
      'setBreakpoint (sb)',
      'clearBreakpoint (cb)'
    ],
    [
      'watch',
      'unwatch',
      'watchers',
      'repl',
      'restart',
      'kill',
      'list',
      'scripts',
      'breakOnException',
      'breakpoints',
      'version'
    ]
  ];
  
  
  var helpMessage = 'Commands: ' + commands.map(function(group) {
    return group.join(', ');
  }).join(',\n');
  
  
  function SourceUnderline(sourceText, position, repl) {
    if (!sourceText) return '';
  
    var head = sourceText.slice(0, position),
        tail = sourceText.slice(position);
  
    // Colourize char if stdout supports colours
    if (repl.useColors) {
      tail = tail.replace(/(.+?)([^\w]|$)/, '\u001b[32m$1\u001b[39m$2');
    }
  
    // Return source line with coloured char at `position`
    return [
      head,
      tail
    ].join('');
  }
  
  
  function SourceInfo(body) {
    var result = body.exception ? 'exception in ' : 'break in ';
  
    if (body.script) {
      if (body.script.name) {
        var name = body.script.name,
            dir = path.resolve() + '/';
  
        // Change path to relative, if possible
        if (name.indexOf(dir) === 0) {
          name = name.slice(dir.length);
        }
  
        result += name;
      } else {
        result += '[unnamed]';
      }
    }
    result += ':';
    result += body.sourceLine + 1;
  
    if (body.exception) result += '\n' + body.exception.text;
  
    return result;
  }
  
  // This class is the repl-enabled debugger interface which is invoked on
  // "node debug"
  function Interface(stdin, stdout, args) {
    var self = this;
  
    this.stdin = stdin;
    this.stdout = stdout;
    this.args = args;
  
    // Two eval modes are available: controlEval and debugEval
    // But controlEval is used by default
    var opts = {
      prompt: 'debug> ',
      input: this.stdin,
      output: this.stdout,
      eval: this.controlEval.bind(this),
      useGlobal: false,
      ignoreUndefined: true
    };
    if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
      opts.terminal = false;
    } else if (parseInt(process.env['NODE_FORCE_READLINE'], 10)) {
      opts.terminal = true;
  
      // Emulate Ctrl+C if we're emulating terminal
      if (!this.stdout.isTTY) {
        process.on('SIGINT', function() {
          self.repl.rli.emit('SIGINT');
        });
      }
    }
    if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
      opts.useColors = false;
    }
    this.repl = repl.start(opts);
  
    // Do not print useless warning
    repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);
  
    // Kill child process when main process dies
    this.repl.on('exit', function() {
      process.exit(0);
    });
  
    // Handle all possible exits
    process.on('exit', this.killChild.bind(this));
    process.once('SIGTERM', process.exit.bind(process, 0));
    process.once('SIGHUP', process.exit.bind(process, 0));
  
    var proto = Interface.prototype,
        ignored = ['pause', 'resume', 'exitRepl', 'handleBreak',
                   'requireConnection', 'killChild', 'trySpawn',
                   'controlEval', 'debugEval', 'print', 'childPrint',
                   'clearline'],
        shortcut = {
          'run': 'r',
          'cont': 'c',
          'next': 'n',
          'step': 's',
          'out': 'o',
          'backtrace': 'bt',
          'setBreakpoint': 'sb',
          'clearBreakpoint': 'cb',
          'pause_': 'pause'
        };
  
    function defineProperty(key, protoKey) {
      // Check arity
      var fn = proto[protoKey].bind(self);
  
      if (proto[protoKey].length === 0) {
        Object.defineProperty(self.repl.context, key, {
          get: fn,
          enumerable: true,
          configurable: false
        });
      } else {
        self.repl.context[key] = fn;
      }
    };
  
    // Copy all prototype methods in repl context
    // Setup them as getters if possible
    for (var i in proto) {
      if (Object.prototype.hasOwnProperty.call(proto, i) &&
          ignored.indexOf(i) === -1) {
        defineProperty(i, i);
        if (shortcut[i]) defineProperty(shortcut[i], i);
      }
    }
  
    this.killed = false;
    this.waiting = null;
    this.paused = 0;
    this.context = this.repl.context;
    this.history = {
      debug: [],
      control: []
    };
    this.breakpoints = [];
    this._watchers = [];
  
    // Run script automatically
    this.pause();
  
    // XXX Need to figure out why we need this delay
    setTimeout(function() {
  
      self.run(function() {
        self.resume();
      });
    }, 10);
  }
  
  
  // Stream control
  
  
  Interface.prototype.pause = function() {
    if (this.killed || this.paused++ > 0) return this;
    this.repl.rli.pause();
    this.stdin.pause();
    return this;
  };
  
  Interface.prototype.resume = function(silent) {
    if (this.killed || this.paused === 0 || --this.paused !== 0) return this;
    this.repl.rli.resume();
    if (silent !== true) {
      this.repl.displayPrompt();
    }
    this.stdin.resume();
  
    if (this.waiting) {
      this.waiting();
      this.waiting = null;
    }
    return this;
  };
  
  
  // Clear current line
  Interface.prototype.clearline = function() {
    if (this.stdout.isTTY) {
      this.stdout.cursorTo(0);
      this.stdout.clearLine(1);
    } else {
      this.stdout.write('\b');
    }
  };
  
  // Print text to output stream
  Interface.prototype.print = function(text, oneline) {
    if (this.killed) return;
    this.clearline();
  
    this.stdout.write(util.isString(text) ? text : util.inspect(text));
  
    if (oneline !== true) {
      this.stdout.write('\n');
    }
  };
  
  // Format and print text from child process
  Interface.prototype.childPrint = function(text) {
    this.print(text.toString().split(/\r\n|\r|\n/g).filter(function(chunk) {
      return chunk;
    }).map(function(chunk) {
      return '< ' + chunk;
    }).join('\n'));
    this.repl.displayPrompt(true);
  };
  
  // Errors formatting
  Interface.prototype.error = function(text) {
    this.print(text);
    this.resume();
  };
  
  
  // Debugger's `break` event handler
  Interface.prototype.handleBreak = function(r) {
    var self = this;
  
    this.pause();
  
    // Save execution context's data
    this.client.currentSourceLine = r.sourceLine;
    this.client.currentSourceLineText = r.sourceLineText;
    this.client.currentSourceColumn = r.sourceColumn;
    this.client.currentFrame = 0;
    this.client.currentScript = r.script && r.script.name;
  
    // Print break data
    this.print(SourceInfo(r));
  
    // Show watchers' values
    this.watchers(true, function(err) {
      if (err) return self.error(err);
  
      // And list source
      self.list(2);
  
      self.resume(true);
    });
  };
  
  
  // Internal method for checking connection state
  Interface.prototype.requireConnection = function() {
    if (!this.client) {
      this.error('App isn\'t running... Try `run` instead');
      return false;
    }
    return true;
  };
  
  
  // Evals
  
  // Used for debugger's commands evaluation and execution
  Interface.prototype.controlEval = function(code, context, filename, callback) {
    try {
      // Repeat last command if empty line are going to be evaluated
      if (this.repl.rli.history && this.repl.rli.history.length > 0) {
        if (code === '\n') {
          code = this.repl.rli.history[0] + '\n';
        }
      }
  
      var result = vm.runInContext(code, context, filename);
  
      // Repl should not ask for next command
      // if current one was asynchronous.
      if (this.paused === 0) return callback(null, result);
  
      // Add a callback for asynchronous command
      // (it will be automatically invoked by .resume() method
      this.waiting = function() {
        callback(null, result);
      };
    } catch (e) {
      callback(e);
    }
  };
  
  // Used for debugger's remote evaluation (`repl`) commands
  Interface.prototype.debugEval = function(code, context, filename, callback) {
    if (!this.requireConnection()) return;
  
    var self = this,
        client = this.client;
  
    // Repl asked for scope variables
    if (code === '.scope') {
      client.reqScopes(callback);
      return;
    }
  
    var frame = client.currentFrame === NO_FRAME ? frame : undefined;
  
    self.pause();
  
    // Request remote evaluation globally or in current frame
    client.reqFrameEval(code, frame, function(err, res) {
      if (err) {
        callback(err);
        self.resume(true);
        return;
      }
  
      // Request object by handles (and it's sub-properties)
      client.mirrorObject(res, 3, function(err, mirror) {
        callback(null, mirror);
        self.resume(true);
      });
    });
  };
  
  
  // Utils
  
  // Adds spaces and prefix to number
  // maxN is a maximum number we should have space for
  function leftPad(n, prefix, maxN) {
    var s = n.toString(),
        nchars = Math.max(2, String(maxN).length) + 1,
        nspaces = nchars - s.length - 1;
  
    for (var i = 0; i < nspaces; i++) {
      prefix += ' ';
    }
  
    return prefix + s;
  }
  
  
  // Commands
  
  
  // Print help message
  Interface.prototype.help = function() {
    this.print(helpMessage);
  };
  
  
  // Run script
  Interface.prototype.run = function() {
    var callback = arguments[0];
  
    if (this.child) {
      this.error('App is already running... Try `restart` instead');
      callback && callback(true);
    } else {
      this.trySpawn(callback);
    }
  };
  
  
  // Restart script
  Interface.prototype.restart = function() {
    if (!this.requireConnection()) return;
  
    var self = this;
  
    self.pause();
    self.killChild();
  
    // XXX need to wait a little bit for the restart to work?
    setTimeout(function() {
      self.trySpawn();
      self.resume();
    }, 1000);
  };
  
  
  // Print version
  Interface.prototype.version = function() {
    if (!this.requireConnection()) return;
  
    var self = this;
  
    this.pause();
    this.client.reqVersion(function(err, v) {
      if (err) {
        self.error(err);
      } else {
        self.print(v);
      }
      self.resume();
    });
  };
  
  // List source code
  Interface.prototype.list = function(delta) {
    if (!this.requireConnection()) return;
  
    delta || (delta = 5);
  
    var self = this,
        client = this.client,
        from = client.currentSourceLine - delta + 1,
        to = client.currentSourceLine + delta + 1;
  
    self.pause();
    client.reqSource(from, to, function(err, res) {
      if (err || !res) {
        self.error('You can\'t list source code right now');
        self.resume();
        return;
      }
  
      var lines = res.source.split('\n');
      for (var i = 0; i < lines.length; i++) {
        var lineno = res.fromLine + i + 1;
        if (lineno < from || lineno > to) continue;
  
        var current = lineno == 1 + client.currentSourceLine,
            breakpoint = client.breakpoints.some(function(bp) {
              return (bp.scriptReq === client.currentScript ||
                      bp.script === client.currentScript) &&
                      bp.line == lineno;
            });
  
        if (lineno == 1) {
          // The first line needs to have the module wrapper filtered out of
          // it.
          var wrapper = require('module').wrapper[0];
          lines[i] = lines[i].slice(wrapper.length);
  
          client.currentSourceColumn -= wrapper.length;
        }
  
        // Highlight executing statement
        var line;
        if (current) {
          line = SourceUnderline(lines[i],
                                 client.currentSourceColumn,
                                 self.repl);
        } else {
          line = lines[i];
        }
  
        var prefixChar = ' ';
        if (current) {
          prefixChar = '>';
        } else if (breakpoint) {
          prefixChar = '*';
        }
  
        self.print(leftPad(lineno, prefixChar, to) + ' ' + line);
      }
      self.resume();
    });
  };
  
  // Print backtrace
  Interface.prototype.backtrace = function() {
    if (!this.requireConnection()) return;
  
    var self = this,
        client = this.client;
  
    self.pause();
    client.fullTrace(function(err, bt) {
      if (err) {
        self.error('Can\'t request backtrace now');
        self.resume();
        return;
      }
  
      if (bt.totalFrames == 0) {
        self.print('(empty stack)');
      } else {
        var trace = [],
            firstFrameNative = bt.frames[0].script.isNative;
  
        for (var i = 0; i < bt.frames.length; i++) {
          var frame = bt.frames[i];
          if (!firstFrameNative && frame.script.isNative) break;
  
          var text = '#' + i + ' ';
          if (frame.func.inferredName && frame.func.inferredName.length > 0) {
            text += frame.func.inferredName + ' ';
          }
          text += path.basename(frame.script.name) + ':';
          text += (frame.line + 1) + ':' + (frame.column + 1);
  
          trace.push(text);
        }
  
        self.print(trace.join('\n'));
      }
  
      self.resume();
    });
  };
  
  
  // First argument tells if it should display internal node scripts or not
  // (available only for internal debugger's functions)
  Interface.prototype.scripts = function() {
    if (!this.requireConnection()) return;
  
    var client = this.client,
        displayNatives = arguments[0] || false,
        scripts = [];
  
    this.pause();
    for (var id in client.scripts) {
      var script = client.scripts[id];
      if (util.isObject(script) && script.name) {
        if (displayNatives ||
            script.name == client.currentScript ||
            !script.isNative) {
          scripts.push(
              (script.name == client.currentScript ? '* ' : '  ') +
              id + ': ' +
              path.basename(script.name)
          );
        }
      }
    }
    this.print(scripts.join('\n'));
    this.resume();
  };
  
  
  // Continue execution of script
  Interface.prototype.cont = function() {
    if (!this.requireConnection()) return;
    this.pause();
  
    var self = this;
    this.client.reqContinue(function(err) {
      if (err) self.error(err);
      self.resume();
    });
  };
  
  
  // Step commands generator
  Interface.stepGenerator = function(type, count) {
    return function() {
      if (!this.requireConnection()) return;
  
      var self = this;
  
      self.pause();
      self.client.step(type, count, function(err, res) {
        if (err) self.error(err);
        self.resume();
      });
    };
  };
  
  
  // Jump to next command
  Interface.prototype.next = Interface.stepGenerator('next', 1);
  
  
  // Step in
  Interface.prototype.step = Interface.stepGenerator('in', 1);
  
  
  // Step out
  Interface.prototype.out = Interface.stepGenerator('out', 1);
  
  
  // Watch
  Interface.prototype.watch = function(expr) {
    this._watchers.push(expr);
  };
  
  // Unwatch
  Interface.prototype.unwatch = function(expr) {
    var index = this._watchers.indexOf(expr);
  
    // Unwatch by expression
    // or
    // Unwatch by watcher number
    this._watchers.splice(index !== -1 ? index : +expr, 1);
  };
  
  // List watchers
  Interface.prototype.watchers = function() {
    var self = this,
        verbose = arguments[0] || false,
        callback = arguments[1] || function() {},
        waiting = this._watchers.length,
        values = [];
  
    this.pause();
  
    if (!waiting) {
      this.resume();
  
      return callback();
    }
  
    this._watchers.forEach(function(watcher, i) {
      self.debugEval(watcher, null, null, function(err, value) {
        values[i] = err ? '<error>' : value;
        wait();
      });
    });
  
    function wait() {
      if (--waiting === 0) {
        if (verbose) self.print('Watchers:');
  
        self._watchers.forEach(function(watcher, i) {
          self.print(leftPad(i, ' ', self._watchers.length - 1) +
                     ': ' + watcher + ' = ' +
                     JSON.stringify(values[i]));
        });
  
        if (verbose) self.print('');
  
        self.resume();
  
        callback(null);
      }
    }
  };
  
  // Break on exception
  Interface.prototype.breakOnException = function breakOnException() {
    if (!this.requireConnection()) return;
  
    var self = this;
  
    // Break on exceptions
    this.pause();
    this.client.reqSetExceptionBreak('all', function(err, res) {
      self.resume();
    });
  };
  
  // Add breakpoint
  Interface.prototype.setBreakpoint = function(script, line,
                                               condition, silent) {
    if (!this.requireConnection()) return;
  
    var self = this,
        scriptId,
        ambiguous;
  
    // setBreakpoint() should insert breakpoint on current line
    if (util.isUndefined(script)) {
      script = this.client.currentScript;
      line = this.client.currentSourceLine + 1;
    }
  
    // setBreakpoint(line-number) should insert breakpoint in current script
    if (util.isUndefined(line) && util.isNumber(script)) {
      line = script;
      script = this.client.currentScript;
    }
  
    if (/\(\)$/.test(script)) {
      // setBreakpoint('functionname()');
      var req = {
        type: 'function',
        target: script.replace(/\(\)$/, ''),
        condition: condition
      };
    } else {
      // setBreakpoint('scriptname')
      if (script != +script && !this.client.scripts[script]) {
        var scripts = this.client.scripts;
        Object.keys(scripts).forEach(function(id) {
          if (scripts[id] &&
              scripts[id].name &&
              scripts[id].name.indexOf(script) !== -1) {
            if (scriptId) {
              ambiguous = true;
            }
            scriptId = id;
          }
        });
      } else {
        scriptId = script;
      }
  
      if (ambiguous) return this.error('Script name is ambiguous');
      if (line <= 0) return this.error('Line should be a positive value');
  
      var req;
      if (scriptId) {
        req = {
          type: 'scriptId',
          target: scriptId,
          line: line - 1,
          condition: condition
        };
      } else {
        this.print('Warning: script \'' + script + '\' was not loaded yet.');
        var escapedPath = script.replace(/([/\\.?*()^${}|[\]])/g, '\\$1');
        var scriptPathRegex = '^(.*[\\/\\\\])?' + escapedPath + '$';
        req = {
          type: 'scriptRegExp',
          target: scriptPathRegex,
          line: line - 1,
          condition: condition
        };
      }
    }
  
    self.pause();
    self.client.setBreakpoint(req, function(err, res) {
      if (err) {
        if (!silent) {
          self.error(err);
        }
      } else {
        if (!silent) {
          self.list(5);
        }
  
        // Try load scriptId and line from response
        if (!scriptId) {
          scriptId = res.script_id;
          line = res.line + 1;
        }
  
        // Remember this breakpoint even if scriptId is not resolved yet
        self.client.breakpoints.push({
          id: res.breakpoint,
          scriptId: scriptId,
          script: (self.client.scripts[scriptId] || {}).name,
          line: line,
          condition: condition,
          scriptReq: script
        });
      }
      self.resume();
    });
  };
  
  // Clear breakpoint
  Interface.prototype.clearBreakpoint = function(script, line) {
    if (!this.requireConnection()) return;
  
    var ambiguous,
        breakpoint,
        index;
  
    this.client.breakpoints.some(function(bp, i) {
      if (bp.scriptId === script ||
          bp.scriptReq === script ||
          (bp.script && bp.script.indexOf(script) !== -1)) {
        if (!util.isUndefined(index)) {
          ambiguous = true;
        }
        if (bp.line === line) {
          index = i;
          breakpoint = bp.id;
          return true;
        }
      }
    });
  
    if (ambiguous) return this.error('Script name is ambiguous');
  
    if (util.isUndefined(breakpoint)) {
      return this.error('Script : ' + script + ' not found');
    }
  
    var self = this,
        req = {
          breakpoint: breakpoint
        };
  
    self.pause();
    self.client.clearBreakpoint(req, function(err, res) {
      if (err) {
        self.error(err);
      } else {
        self.client.breakpoints.splice(index, 1);
        self.list(5);
      }
      self.resume();
    });
  };
  
  
  // Show breakpoints
  Interface.prototype.breakpoints = function() {
    if (!this.requireConnection()) return;
  
    this.pause();
    var self = this;
    this.client.listbreakpoints(function(err, res) {
      if (err) {
        self.error(err);
      } else {
        self.print(res);
        self.resume();
      }
    });
  };
  
  
  // Pause child process
  Interface.prototype.pause_ = function() {
    if (!this.requireConnection()) return;
  
    var self = this,
        cmd = 'process._debugPause();';
  
    this.pause();
    this.client.reqFrameEval(cmd, NO_FRAME, function(err, res) {
      if (err) {
        self.error(err);
      } else {
        self.resume();
      }
    });
  };
  
  
  // Kill child process
  Interface.prototype.kill = function() {
    if (!this.child) return;
    this.killChild();
  };
  
  
  // Activate debug repl
  Interface.prototype.repl = function() {
    if (!this.requireConnection()) return;
  
    var self = this;
  
    self.print('Press Ctrl + C to leave debug repl');
  
    // Don't display any default messages
    var listeners = this.repl.rli.listeners('SIGINT').slice(0);
    this.repl.rli.removeAllListeners('SIGINT');
  
    // Exit debug repl on Ctrl + C
    this.repl.rli.once('SIGINT', function() {
      // Restore all listeners
      process.nextTick(function() {
        listeners.forEach(function(listener) {
          self.repl.rli.on('SIGINT', listener);
        });
      });
  
      // Exit debug repl
      self.exitRepl();
    });
  
    // Set new
    this.repl.eval = this.debugEval.bind(this);
    this.repl.context = {};
  
    // Swap history
    this.history.control = this.repl.rli.history;
    this.repl.rli.history = this.history.debug;
  
    this.repl.prompt = '> ';
    this.repl.rli.setPrompt('> ');
    this.repl.displayPrompt();
  };
  
  
  // Exit debug repl
  Interface.prototype.exitRepl = function() {
    // Restore eval
    this.repl.eval = this.controlEval.bind(this);
  
    // Swap history
    this.history.debug = this.repl.rli.history;
    this.repl.rli.history = this.history.control;
  
    this.repl.context = this.context;
    this.repl.prompt = 'debug> ';
    this.repl.rli.setPrompt('debug> ');
    this.repl.displayPrompt();
  };
  
  
  // Quit
  Interface.prototype.quit = function() {
    this.killChild();
    process.exit(0);
  };
  
  
  // Kills child process
  Interface.prototype.killChild = function() {
    if (this.child) {
      this.child.kill();
      this.child = null;
    }
  
    if (this.client) {
      // Save breakpoints
      this.breakpoints = this.client.breakpoints;
  
      this.client.destroy();
      this.client = null;
    }
  };
  
  
  // Spawns child process (and restores breakpoints)
  Interface.prototype.trySpawn = function(cb) {
    var self = this,
        breakpoints = this.breakpoints || [],
        port = exports.port,
        host = 'localhost',
        childArgs = this.args;
  
    this.killChild();
    assert(!this.child);
  
    if (this.args.length === 2) {
      var match = this.args[1].match(/^([^:]+):(\d+)$/);
  
      if (match) {
        // Connecting to remote debugger
        // `node debug localhost:5858`
        host = match[1];
        port = parseInt(match[2], 10);
        this.child = {
          kill: function() {
            // TODO Do we really need to handle it?
          }
        };
      }
    } else if (this.args.length === 3) {
      // `node debug -p pid`
      if (this.args[1] === '-p' && /^\d+$/.test(this.args[2])) {
        this.child = {
          kill: function() {
            // TODO Do we really need to handle it?
          }
        };
        process._debugProcess(parseInt(this.args[2], 10));
      } else {
        var match = this.args[1].match(/^--port=(\d+)$/);
        if (match) {
          // Start debugger on custom port
          // `node debug --port=5858 app.js`
          port = parseInt(match[1], 10);
          childArgs = ['--debug-brk=' + port].concat(this.args.slice(2));
        }
      }
    }
  
    this.child = spawn(process.execPath, childArgs);
  
    this.child.stdout.on('data', this.childPrint.bind(this));
    this.child.stderr.on('data', this.childPrint.bind(this));
  
    this.pause();
  
    var client = self.client = new Client(),
        connectionAttempts = 0;
  
    client.once('ready', function() {
      self.stdout.write(' ok\n');
  
      // Restore breakpoints
      breakpoints.forEach(function(bp) {
        self.print('Restoring breakpoint ' + bp.scriptReq + ':' + bp.line);
        self.setBreakpoint(bp.scriptReq, bp.line, bp.condition, true);
      });
  
      client.on('close', function() {
        self.pause();
        self.print('program terminated');
        self.resume();
        self.client = null;
        self.killChild();
      });
  
      if (cb) cb();
      self.resume();
    });
  
    client.on('unhandledResponse', function(res) {
      self.pause();
      self.print('\nunhandled res:' + JSON.stringify(res));
      self.resume();
    });
  
    client.on('break', function(res) {
      self.handleBreak(res.body);
    });
  
    client.on('exception', function(res) {
      self.handleBreak(res.body);
    });
  
    client.on('error', connectError);
    function connectError() {
      // If it's failed to connect 4 times then don't catch the next error
      if (connectionAttempts >= 10) {
        client.removeListener('error', connectError);
      }
      setTimeout(attemptConnect, 500);
    }
  
    function attemptConnect() {
      ++connectionAttempts;
      self.stdout.write('.');
      client.connect(port, host);
    }
  
    this.child.stderr.once('data', function() {
      setImmediate(function() {
        self.print('connecting to port ' + port + '..', true);
        attemptConnect();
      });
    });
  };
  