"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UISession = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ var _readline = require("readline"); var _readline2 = _interopRequireDefault(_readline); var _child_process = require("child_process"); var _child_process2 = _interopRequireDefault(_child_process); var _vscodeDebugprotocol = require("vscode-debugprotocol"); var DebugProtocol = _interopRequireWildcard(_vscodeDebugprotocol); var _DataHandler = require("./DataHandler.js"); var _DebuggerConstants = require("./../common/DebuggerConstants"); var _types = require("./../common/types.js"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } //separator for messages according to the protocol var TWO_CRLF = "\r\n\r\n"; /* Represents one debugging session in the CLI. * Read in user input from the command line, parses the input into commands, * sends the commands to the adapter and process any responses */ var UISession = exports.UISession = function () { function UISession(proc, args) { _classCallCheck(this, UISession); this._proc = proc; this._adapterPath = args.adapterPath; this._prepackRuntime = args.prepackRuntime; this._sourceFile = args.sourceFile; this._prepackArguments = args.prepackArguments; this._sequenceNum = 1; this._invalidCount = 0; this._dataHandler = new _DataHandler.DataHandler(); this._prepackWaiting = false; this._prepackLaunched = false; } // the parent (i.e. ui) process //path to the debug adapter // the child (i.e. adapter) process // id number for each message sent // interface to read in input from the CLI client // number of invalid commands // Prepack runtime command (e.g. lib/prepack-cli.js) // input source file to Prepack // arguments to start Prepack with // handler for any received messages // flag whether Prepack is waiting for a command // flag whether Prepack has been launched _createClass(UISession, [{ key: "_startAdapter", value: function _startAdapter() { var _this = this; var adapterArgs = [this._adapterPath]; this._adapterProcess = _child_process2.default.spawn("node", adapterArgs); this._proc.on("exit", function () { _this.shutdown(); }); this._proc.on("SIGINT", function () { _this.shutdown(); }); this._adapterProcess.stdout.on("data", function (data) { //handle the received data _this._dataHandler.handleData(data, _this._processMessage.bind(_this)); }); this._adapterProcess.stderr.on("data", function (data) { console.error(data.toString()); _this.shutdown(); }); } // called from data handler to process a received message }, { key: "_processMessage", value: function _processMessage(message) { var _this2 = this; try { var msg = JSON.parse(message); if (msg.type === "event") { this._processEvent(msg); } else if (msg.type === "response") { this._processResponse(msg); } } catch (e) { console.error(e); console.error("Invalid message: " + message.slice(0, 1000)); } //ask the user for the next command if (this._prepackLaunched && this._prepackWaiting) { this._reader.question("(dbg) ", function (input) { _this2._dispatch(input); }); } } }, { key: "_processEvent", value: function _processEvent(event) { if (event.event === "initialized") { // the adapter is ready to accept any persisted debug information // (e.g. persisted breakpoints from previous sessions). the CLI var configDoneArgs = {}; this._sendConfigDoneRequest(configDoneArgs); } else if (event.event === "output") { this._uiOutput("Prepack output:\n" + event.body.output); } else if (event.event === "terminated") { this._uiOutput("Prepack exited! Shutting down..."); this.shutdown(); } else if (event.event === "stopped") { this._prepackWaiting = true; if (event.body) { this._uiOutput(event.body.reason); } } } }, { key: "_processResponse", value: function _processResponse(response) { if (response.command === "initialize") { this._processInitializeResponse(response); } else if (response.command === "launch") { this._processLaunchResponse(response); } else if (response.command === "threads") { this._processThreadsResponse(response); } else if (response.command === "stackTrace") { //flow doesn't have type refinement for interfaces, so must do a cast here this._processStackTraceResponse(response); } else if (response.command === "scopes") { this._processScopesResponse(response); } else if (response.command === "variables") { this._processVariablesResponse(response); } else if (response.command === "evaluate") { this._processEvaluateResponse(response); } } }, { key: "_processScopesResponse", value: function _processScopesResponse(response) { var scopes = response.body.scopes; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = scopes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var scope = _step.value; this._uiOutput(scope.name + " " + scope.variablesReference); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } }, { key: "_processInitializeResponse", value: function _processInitializeResponse(response) { var launchArgs = { prepackRuntime: this._prepackRuntime, sourceFile: this._sourceFile, prepackArguments: this._prepackArguments }; this._sendLaunchRequest(launchArgs); } }, { key: "_processLaunchResponse", value: function _processLaunchResponse(response) { var _this3 = this; this._uiOutput("Prepack is ready"); this._prepackLaunched = true; this._prepackWaiting = true; // start reading requests from the user this._reader.question("(dbg) ", function (input) { _this3._dispatch(input); }); } }, { key: "_processStackTraceResponse", value: function _processStackTraceResponse(response) { var frames = response.body.stackFrames; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = frames[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var frame = _step2.value; if (frame.source && frame.source.path) { this._uiOutput(frame.id + ": " + frame.name + " " + frame.source.path + " " + frame.line + ":" + frame.column); } else { this._uiOutput(frame.id + ": " + frame.name + " unknown source"); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } }, { key: "_processThreadsResponse", value: function _processThreadsResponse(response) { var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = response.body.threads[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var thread = _step3.value; this._uiOutput(thread.id + ": " + thread.name); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } }, { key: "_processVariablesResponse", value: function _processVariablesResponse(response) { var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = response.body.variables[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var variable = _step4.value; if (variable.variablesReference === 0) { // 0 means there are not more nested variables to return this._uiOutput(variable.name + ": " + variable.value); } else { this._uiOutput(variable.name + ": " + variable.value + " " + variable.variablesReference); } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } }, { key: "_processEvaluateResponse", value: function _processEvaluateResponse(response) { var evalInfo = response.body; this._uiOutput("Type: " + (evalInfo.type || "unknown")); this._uiOutput(evalInfo.result); this._uiOutput("Variables Reference: " + evalInfo.variablesReference); } // execute a command if it is valid // returns whether the command was valid }, { key: "_executeCommand", value: function _executeCommand(input) { var parts = input.split(" "); var command = parts[0]; // for testing purposes, init and configDone are made into user commands // they can be done from the adapter without user input switch (command) { case "run": // format: run if (parts.length !== 1) return false; var continueArgs = { // Prepack will only have 1 thread, this argument will be ignored threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendContinueRequest(continueArgs); break; case "breakpoint": // format: breakpoint add ? if (parts.length !== 4 && parts.length !== 5) return false; if (parts[1] === "add") { var filePath = parts[2]; var line = parseInt(parts[3], 10); if (isNaN(line)) return false; var column = 0; if (parts.length === 5) { column = parseInt(parts[4], 10); if (isNaN(column)) return false; } this._sendBreakpointRequest(filePath, line, column); } break; case "stackframes": // format: stackFrames var stackFrameArgs = { // Prepack will only have 1 thread, this argument will be ignored threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStackFramesRequest(stackFrameArgs); break; case "threads": if (parts.length !== 1) return false; this._sendThreadsRequest(); break; case "scopes": if (parts.length !== 2) return false; var frameId = parseInt(parts[1], 10); if (isNaN(frameId)) return false; var scopesArgs = { frameId: frameId }; this._sendScopesRequest(scopesArgs); break; case "variables": if (parts.length !== 2) return false; var varRef = parseInt(parts[1], 10); if (isNaN(varRef)) return false; var variableArgs = { variablesReference: varRef }; this._sendVariablesRequest(variableArgs); break; case "stepInto": if (parts.length !== 1) return false; var stepIntoArgs = { threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStepIntoRequest(stepIntoArgs); break; case "stepOver": if (parts.length !== 1) return false; var stepOverArgs = { threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStepOverRequest(stepOverArgs); break; case "eval": if (parts.length < 2) return false; var evalFrameId = parseInt(parts[1], 10); if (isNaN(evalFrameId)) { var expression = parts.slice(1).join(" "); var evaluateArgs = { expression: expression }; this._sendEvaluateRequest(evaluateArgs); } else { var _expression = parts.slice(2).join(" "); var _evaluateArgs = { expression: _expression, frameId: evalFrameId }; this._sendEvaluateRequest(_evaluateArgs); } break; default: // invalid command return false; } return true; } // parses the user input into a command and executes it }, { key: "_dispatch", value: function _dispatch(input) { var _this4 = this; if (input === "exit") { this.shutdown(); } var success = this._executeCommand(input); if (!success) { // input was invalid this._invalidCount++; //prevent stack overflow from recursion if (this._invalidCount >= 10) { console.error("Too many invalid commands, shutting down..."); this.shutdown(); } console.error("Invalid command: " + input); this._reader.question("(dbg) ", function (line) { _this4._dispatch(line); }); } //reset the invalid command counter this._invalidCount = 0; } // tell the adapter about some configuration details }, { key: "_sendInitializeRequest", value: function _sendInitializeRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "initialize", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } // tell the adapter to start Prepack }, { key: "_sendLaunchRequest", value: function _sendLaunchRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "launch", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } // tell the adapter that configuration is done so it can expect other commands }, { key: "_sendConfigDoneRequest", value: function _sendConfigDoneRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "configurationDone", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } // tell the adapter to continue running Prepack }, { key: "_sendContinueRequest", value: function _sendContinueRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "continue", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); this._prepackWaiting = false; } }, { key: "_sendBreakpointRequest", value: function _sendBreakpointRequest(filePath, line) { var column = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; var source = { path: filePath }; var breakpoint = { line: line, column: column }; var args = { source: source, breakpoints: [breakpoint] }; var message = { type: "request", seq: this._sequenceNum, command: "setBreakpoints", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendStackFramesRequest", value: function _sendStackFramesRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "stackTrace", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendThreadsRequest", value: function _sendThreadsRequest() { var message = { type: "request", seq: this._sequenceNum, command: "threads" }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendScopesRequest", value: function _sendScopesRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "scopes", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendVariablesRequest", value: function _sendVariablesRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "variables", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendStepIntoRequest", value: function _sendStepIntoRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "stepIn", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendStepOverRequest", value: function _sendStepOverRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "next", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } }, { key: "_sendEvaluateRequest", value: function _sendEvaluateRequest(args) { var message = { type: "request", seq: this._sequenceNum, command: "evaluate", arguments: args }; var json = JSON.stringify(message); this._packageAndSend(json); } // write out a message to the adapter on stdout }, { key: "_packageAndSend", value: function _packageAndSend(message) { // format: Content-Length: separator this._adapterProcess.stdin.write("Content-Length: " + Buffer.byteLength(message, "utf8") + TWO_CRLF + message, "utf8"); this._sequenceNum++; } }, { key: "_uiOutput", value: function _uiOutput(message) { console.log(message); } }, { key: "serve", value: function serve() { this._uiOutput("Debugger is starting up Prepack..."); // Set up the adapter connection this._startAdapter(); // send an initialize request to the adapter to fetch some configuration details var initArgs = { // a unique name for each UI (e.g Nuclide, VSCode, CLI) clientID: _DebuggerConstants.DebuggerConstants.CLI_CLIENTID, // a unique name for each adapter adapterID: "Prepack-Debugger-Adapter", linesStartAt1: true, columnsStartAt1: true, supportsVariableType: true, supportsVariablePaging: false, supportsRunInTerminalRequest: false, pathFormat: "path" }; this._sendInitializeRequest(initArgs); this._reader = _readline2.default.createInterface({ input: this._proc.stdin, output: this._proc.stdout }); } }, { key: "shutdown", value: function shutdown() { this._reader.close(); this._adapterProcess.kill(); this._proc.exit(0); } }]); return UISession; }(); //# sourceMappingURL=UISession.js.map