init
This commit is contained in:
commit
35f22d87a0
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.vsix
|
||||||
|
out
|
||||||
|
package-lock.json
|
||||||
|
node_modules
|
BIN
.vscode/ipch/3c4d88325f9a2662/mmap_address.bin
vendored
Normal file
BIN
.vscode/ipch/3c4d88325f9a2662/mmap_address.bin
vendored
Normal file
Binary file not shown.
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Extension",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeExecutable": "${execPath}",
|
||||||
|
"args": [
|
||||||
|
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||||
|
],
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/out/**/*.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopJS Sample",
|
||||||
|
"type": "topjs",
|
||||||
|
"request": "launch",
|
||||||
|
"port": 30992,
|
||||||
|
"program": "${workspaceFolder}/${command:getProgramName}",
|
||||||
|
"runtimeExecutable": "topjs3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Place your settings in this file to overwrite default and user settings.FALSE
|
||||||
|
{
|
||||||
|
"javascript.validate.enable": false,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"editor.insertSpaces": true
|
||||||
|
}
|
18
.vscode/tasks.json
vendored
Normal file
18
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "watch",
|
||||||
|
"problemMatcher": "$tsc-watch",
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "never"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
build.js
Normal file
7
build.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const ncp = require('ncp');
|
||||||
|
|
||||||
|
ncp('./src', './out', (err) => {
|
||||||
|
if(err) {
|
||||||
|
return console.error(err);
|
||||||
|
}
|
||||||
|
});
|
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
117
package.json
Normal file
117
package.json
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"name": "topjs-debugger",
|
||||||
|
"displayName": "TopJS Debugger",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"publisher": "AbbyCin",
|
||||||
|
"description": "TopJS debugger extension for VS Code.",
|
||||||
|
"author": {
|
||||||
|
"name": "AbbyCin",
|
||||||
|
"email": "abbytsing@gmail.com"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"multi-root ready"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.30.0",
|
||||||
|
"node": "^8.9.3"
|
||||||
|
},
|
||||||
|
"icon": "favicon.png",
|
||||||
|
"categories": [
|
||||||
|
"Debuggers"
|
||||||
|
],
|
||||||
|
"private": true,
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Microsoft/vscode-mock-debug.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Microsoft/vscode-mock-debug/issues"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||||
|
"build": "node ./build.js",
|
||||||
|
"package": "vsce package",
|
||||||
|
"publish": "vsce publish"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"await-notify": "^1.0.1",
|
||||||
|
"command-exists": "^1.2.8",
|
||||||
|
"vscode-debugadapter": "^1.33.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "8.9.3",
|
||||||
|
"vscode": "1.1.21",
|
||||||
|
"vscode-debugadapter-testsupport": "1.33.0",
|
||||||
|
"vsce": "1.53.2",
|
||||||
|
"ncp": "2.0.0"
|
||||||
|
},
|
||||||
|
"main": "./src/extension",
|
||||||
|
"activationEvents": [
|
||||||
|
"onDebug",
|
||||||
|
"onCommand:extension.topjs-debugger.getProgramPath"
|
||||||
|
],
|
||||||
|
"contributes": {
|
||||||
|
"breakpoints": [
|
||||||
|
{
|
||||||
|
"language": "javascript"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"debuggers": [
|
||||||
|
{
|
||||||
|
"type": "topjs",
|
||||||
|
"label": "TopJS Debugger",
|
||||||
|
"program": "./src/debugAdapter.js",
|
||||||
|
"runtime": "node",
|
||||||
|
"configurationAttributes": {
|
||||||
|
"launch": {
|
||||||
|
"required": [
|
||||||
|
"program",
|
||||||
|
"runtimeExecutable",
|
||||||
|
"port"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"program": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Absolute path to a javascript file.",
|
||||||
|
"default": "${workspaceFolder}/${command:getProgramPath}"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"descrition": "Port for debugger backend.",
|
||||||
|
"default": 30992
|
||||||
|
},
|
||||||
|
"runtimeExecutable": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "topjs3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"initialConfigurations": [
|
||||||
|
{
|
||||||
|
"type": "topjs",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Debugger",
|
||||||
|
"program": "${workspaceFolder}/${command:getProgramPath}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configurationSnippets": [
|
||||||
|
{
|
||||||
|
"label": "TopJS Debug: Launch",
|
||||||
|
"description": "A new configuration for 'debugging' a user selected javascript file.",
|
||||||
|
"body": {
|
||||||
|
"type": "topjs",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Debugger2",
|
||||||
|
"program": "^\"\\${workspaceFolder}/\\${command:getProgramPath}\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": {
|
||||||
|
"getProgramPath": "extension.topjs-debugger.getProgramPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
2
src/debugAdapter.js
Normal file
2
src/debugAdapter.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
const topjsDebugger = require('./topjsDebug');
|
||||||
|
topjsDebugger.TopJSDebugSession.run(topjsDebugger.TopJSDebugSession);
|
64
src/extension.js
Normal file
64
src/extension.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
const vscode = require('vscode');
|
||||||
|
const topjsDebug = require('./topjsDebug');
|
||||||
|
const net = require('net');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function activate(context) {
|
||||||
|
context.subscriptions.push(vscode.commands.registerCommand('extension.topjs-debugger.getProgramPath', config => {
|
||||||
|
var p = vscode.window.activeTextEditor.document.fileName;
|
||||||
|
return path.basename(p);
|
||||||
|
}));
|
||||||
|
const provider = new TopJSConfigurationProvider();
|
||||||
|
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('topjs', provider));
|
||||||
|
var x = true;
|
||||||
|
if(x) {
|
||||||
|
const factory = new TopJSDebugAdapterDescriptorFactory();
|
||||||
|
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('topjs', factory));
|
||||||
|
context.subscriptions.push(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivate() {}
|
||||||
|
|
||||||
|
exports.activate = activate;
|
||||||
|
exports.deactivate = deactivate;
|
||||||
|
|
||||||
|
class TopJSConfigurationProvider {
|
||||||
|
resolveDebugConfigguration(folder, config, token) {
|
||||||
|
if(!config.type && !config.request && !config.name) {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if(editor && editor.document.languageId == 'javascript') {
|
||||||
|
config.type = 'topjs';
|
||||||
|
config.name = 'Launch Debug';
|
||||||
|
config.request = 'launch';
|
||||||
|
config.program = '${file}';
|
||||||
|
config.stopOnEntry = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!config.program) {
|
||||||
|
return vscode.window.showInformationMessage("Cannot find a program to debug").then(_ => {
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TopJSDebugAdapterDescriptorFactory {
|
||||||
|
createDebugAdapterDescriptor(session, executable) {
|
||||||
|
if(!this.server) {
|
||||||
|
this.server = net.createServer(socket => {
|
||||||
|
const session = new topjsDebug.TopJSDebugSession();
|
||||||
|
session.setRunAsServer(true);
|
||||||
|
session.start(socket, socket);
|
||||||
|
}).listen(0); // listen on random port
|
||||||
|
}
|
||||||
|
return new vscode.DebugAdapterServer(this.server.address().port);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
if(this.server) {
|
||||||
|
this.server.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
426
src/topjsDebug.js
Normal file
426
src/topjsDebug.js
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
const vscode_debugadapter = require('vscode-debugadapter');
|
||||||
|
const Path = require('path');
|
||||||
|
const topjsRuntime = require('./topjsRuntime');
|
||||||
|
const { Subject } = require('await-notify');
|
||||||
|
const spawn = require('child_process');
|
||||||
|
const process = require('process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const cmd_exists = require('command-exists').sync;
|
||||||
|
const vscode = require('vscode');
|
||||||
|
|
||||||
|
function fold_varg(...args) {
|
||||||
|
let buf = [];
|
||||||
|
for(let i = 0; i < args.length; ++i) {
|
||||||
|
if(typeof args[i] === 'object') {
|
||||||
|
try {
|
||||||
|
buf.push(Buffer.from(JSON.stringify(args[i], null, ' ')));
|
||||||
|
} catch(e) {
|
||||||
|
buf.push(Buffer.from(''+args[i]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.push(Buffer.from(''+args[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Buffer.concat(buf).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TopJSDebugSession extends vscode_debugadapter.LoggingDebugSession {
|
||||||
|
/**
|
||||||
|
* this class re-write necessary requests of base class, and register
|
||||||
|
* necessary callback handlers for interacting with debugger backend.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
// setup log file path for base class
|
||||||
|
super(Path.join(os.tmpdir(), "topjs-debug.txt"));
|
||||||
|
// for variable inspection
|
||||||
|
this._variablehandles = new vscode_debugadapter.Handles();
|
||||||
|
// for async event notify
|
||||||
|
this._configurationDone = new Subject();
|
||||||
|
// source file line start at 0
|
||||||
|
this.setDebuggerLinesStartAt1(false);
|
||||||
|
// source file column start at 0
|
||||||
|
this.setDebuggerColumnsStartAt1(false);
|
||||||
|
|
||||||
|
this._cachedBreakpoints = {};
|
||||||
|
|
||||||
|
// runtime instance for interacting with debugger backend
|
||||||
|
this._runtime = new topjsRuntime.topjsRuntime();
|
||||||
|
this._runtime.setLogger(this);
|
||||||
|
|
||||||
|
// runtime recieved 'stopOnEntry' event from debugger backend, then
|
||||||
|
// forward to vscode gui by emitting event via debugger adapter
|
||||||
|
this._runtime.on(topjsRuntime.dEvents.stopped, (data) => {
|
||||||
|
this.sendEvent(new vscode_debugadapter.StoppedEvent(data.body.reason, TopJSDebugSession.THREAD_ID));
|
||||||
|
});
|
||||||
|
|
||||||
|
this._runtime.on(topjsRuntime.dEvents.continued, (data) => {
|
||||||
|
this.sendEvent(new vscode_debugadapter.ContinuedEvent(TopJSDebugSession.THREAD_ID));
|
||||||
|
});
|
||||||
|
|
||||||
|
this._runtime.on(topjsRuntime.dEvents.terminated, (data) => {
|
||||||
|
this.sendEvent(new vscode_debugadapter.TerminatedEvent(false)); // don't restart
|
||||||
|
});
|
||||||
|
|
||||||
|
this._runtime.on(topjsRuntime.dEvents.output, (data) => {
|
||||||
|
const e = new vscode_debugadapter.OutputEvent(`${data.body.output}\n`, data.body.category);
|
||||||
|
this.sendEvent(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._runtime.on(topjsRuntime.dEvents.breakpoint, (data) => {
|
||||||
|
this.sendEvent(new vscode_debugadapter.BreakpointEvent(data.body.reason, {
|
||||||
|
verified: data.body.breakpoint.verified,
|
||||||
|
id: data.body.breakpoint.id
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
this._runtime.on(topjsRuntime.dEvents.end, (body) => {
|
||||||
|
this.sendEvent(new vscode_debugadapter.TerminatedEvent());
|
||||||
|
});
|
||||||
|
|
||||||
|
this._runtime.on('connected', () => {
|
||||||
|
this.setBreakPointsRequest(this._cachedBreakpoints.response, this._cachedBreakpoints.args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_log_impl(type, ...args) {
|
||||||
|
const s = fold_varg(...args);
|
||||||
|
const e = new vscode_debugadapter.OutputEvent(`${s}`, type);
|
||||||
|
this.sendEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(...args) {
|
||||||
|
this._log_impl('console', ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
info(...args) {
|
||||||
|
this._log_impl('stdout', ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
err(...args) {
|
||||||
|
this._log_impl('stderr', ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSource(filepath) {
|
||||||
|
// backend must return absolute path of debugging script.
|
||||||
|
if(!fs.existsSync(filepath)) {
|
||||||
|
if(this._program.indexOf(filepath) >= 0) {
|
||||||
|
filepath = this._program;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new vscode_debugadapter.Source(Path.basename(filepath), this.convertDebuggerPathToClient(filepath), undefined, undefined, 'topjs-adapter-data');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* response: initial configuration report debugger capabilities
|
||||||
|
* args: ignored in base class
|
||||||
|
*/
|
||||||
|
initializeRequest(response, args) {
|
||||||
|
response.body = response.body || {};
|
||||||
|
response.body.supportsConfigurationDoneRequest = true;
|
||||||
|
// This default debug adapter does not support hovers based on the 'evaluate' request.
|
||||||
|
response.body.supportsEvaluateForHovers = false;
|
||||||
|
// This default debug adapter does not support the 'restart' request.
|
||||||
|
// if false, adapter will terminate process and spawn a new one.
|
||||||
|
response.body.supportsRestartRequest = true;
|
||||||
|
this.sendResponse(response);
|
||||||
|
this.sendEvent(new vscode_debugadapter.InitializedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* we have to overwrite this function, since we set true in initializeRequest
|
||||||
|
*/
|
||||||
|
configurationDoneRequest(response, args) {
|
||||||
|
super.configurationDoneRequest(response, args);
|
||||||
|
this._configurationDone.notify(); // notifiy launchRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
isExtensionHost(args) {
|
||||||
|
return args.adapterID === 'extensionHost2' || args.adapterID === 'extensionHost';
|
||||||
|
}
|
||||||
|
|
||||||
|
terminateSession(reason) {
|
||||||
|
const processId = this._backend.pid;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const windir = process.env['WINDIR'] || 'C:\\Windows';
|
||||||
|
const TASK_KILL = Path.join(windir, 'System32', 'taskkill.exe');
|
||||||
|
// when killing a process in Windows its child processes are *not* killed but become root processes.
|
||||||
|
// Therefore we use TASKKILL.EXE
|
||||||
|
try {
|
||||||
|
spawn.execSync(`${TASK_KILL} /F /T /PID ${processId}`);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// on linux and OS X we kill all direct and indirect child processes as well
|
||||||
|
try {
|
||||||
|
const cmd = 'kill';
|
||||||
|
spwan.spawnSync(cmd, [processId.toString()]);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnBackend(args) {
|
||||||
|
let argv = [`--remote-debugging-port=${args.port}`, args.program];
|
||||||
|
this._backend = spawn.spawn(args.runtimeExecutable, argv);
|
||||||
|
this._backend.on('exit', () => {
|
||||||
|
const msg = 'debugger backend exited';
|
||||||
|
if(this.isExtensionHost()) {
|
||||||
|
this.terminateSession(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._backend.on('close', (code) => {
|
||||||
|
const msg = `debugger backend exit with code ${code}`;
|
||||||
|
if(!this.isExtensionHost()) {
|
||||||
|
this.terminateSession(msg);
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
this._backend.stdout.on('data', (data) => {
|
||||||
|
this.log(data.toString());
|
||||||
|
});
|
||||||
|
this._backend.stderr.on('data', (data) => {
|
||||||
|
this.log(data.toString());
|
||||||
|
});
|
||||||
|
this._backend.on('error', (err) => {
|
||||||
|
this.err(err.message());
|
||||||
|
this.err('main process exit...');
|
||||||
|
this.sendEvent(new vscode_debugadapter.TerminatedEvent());
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
this.log([args.runtimeExecutable, `--remote-debugging-port=${args.port}`, Path.basename(args.program)].join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
validateArgs(args) {
|
||||||
|
if(!fs.existsSync(args.runtimeExecutable) && !cmd_exists(args.runtimeExecutable)) {
|
||||||
|
this.err(`${args.runtimeExecutable} not found`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!args.program) {
|
||||||
|
this.err('no program found, please add "program" filed in "launch.json"');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!fs.existsSync(args.program)) {
|
||||||
|
this.err(`can't find file: ${args.program}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchRequest(response, args) {
|
||||||
|
if(!args.runtimeExecutable) {
|
||||||
|
args.runtimeExecutable = 'topjs3';
|
||||||
|
}
|
||||||
|
if(!args.port) {
|
||||||
|
args.port = 30992;
|
||||||
|
}
|
||||||
|
if(!this.validateArgs(args)) {
|
||||||
|
this.sendEvent(new vscode_debugadapter.TerminatedEvent());
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
this._args = args;
|
||||||
|
this._program = args.program;
|
||||||
|
vscode_debugadapter.logger.setup(args.trace ? vscode_debugadapter.Logger.LogLevel.Verbose : vscode_debugadapter.Logger.LogLevel.Stop, false);
|
||||||
|
this.spawnBackend(args);
|
||||||
|
this._configurationDone.wait().then(() => {
|
||||||
|
this._runtime.start(args);
|
||||||
|
this.sendResponse(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectRequest(response, args) {
|
||||||
|
this.terminateSession('disconnected');
|
||||||
|
this._runtime.quit();
|
||||||
|
super.disconnectRequest(response, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// below is the real debugging operation we support
|
||||||
|
setBreakPointsRequest(response, args) {
|
||||||
|
if(!this._runtime || !this._runtime.isStarted()) {
|
||||||
|
this._cachedBreakpoints = {"response": response, "args": args};
|
||||||
|
response.body.breakpoints = [];
|
||||||
|
response.success = true;
|
||||||
|
return this.sendRequest(response);
|
||||||
|
}
|
||||||
|
// args.line is deprecated, ignore.
|
||||||
|
args.breakpoints = args.breakpoints.map(b => {
|
||||||
|
b.line = this.convertClientLineToDebugger(b.line);
|
||||||
|
return b;
|
||||||
|
});
|
||||||
|
this._runtime.setBreakpointsRequest(args, response, (resp, breakpoints) => {
|
||||||
|
// breakpoints is a JSON object
|
||||||
|
if(!breakpoints.success) {
|
||||||
|
this.sendErrorResponse(new vscode_debugadapter.Response(breakpoints), JSON.stringify(breakpoints));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const r = breakpoints.body.breakpoints.map(breakpoint => {
|
||||||
|
let {verified, line, id} = breakpoint;
|
||||||
|
const bp = new vscode_debugadapter.Breakpoint(verified, this.convertDebuggerLineToClient(line));
|
||||||
|
bp.id = id;
|
||||||
|
|
||||||
|
return bp;
|
||||||
|
});
|
||||||
|
resp.body = {
|
||||||
|
breakpoints: r
|
||||||
|
}
|
||||||
|
this.sendResponse(resp); // send to frontend
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
continueRequest(response, args) {
|
||||||
|
// args.threadId is ignore, since we only support singel thread debugging
|
||||||
|
this._runtime.continueRequest(args, response, (resp, data) => {
|
||||||
|
resp.body = {
|
||||||
|
threadId: TopJSDebugSession.THREAD_ID, // ignore response id
|
||||||
|
allThreadsContinued: data
|
||||||
|
};
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseRequest(response, args) {
|
||||||
|
this._runtime.pauseRequest(args, response, (resp, data) => {
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not support, treat as nextRequest, after that, a `stopped` event must
|
||||||
|
// return from backend
|
||||||
|
stepInRequest(response, args) {
|
||||||
|
this._runtime.stepInRequest(args, response, (resp, data) => {
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stepOutRequest(response, args) {
|
||||||
|
this._runtime.stepOutRequest(args, response, (resp, data) => {
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The debug adapter first sends the response and then a ‘stopped’
|
||||||
|
// event (with reason ‘step’) after the step has completed.
|
||||||
|
nextRequest(response, args) {
|
||||||
|
this._runtime.nextRequest(args, response, (resp, data) => {
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves all child variables for the given variable reference.
|
||||||
|
variablesRequest(response, args) {
|
||||||
|
//const id = this._variablehandles.get(args.variablesReference);
|
||||||
|
// what's variablesReference ???
|
||||||
|
// it's obtain from `scopesRequest` which is contextId.
|
||||||
|
this._runtime.variablesRequest(args, response, (resp, data) => {
|
||||||
|
resp.body = {
|
||||||
|
variables: data.body.variables
|
||||||
|
};
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request returns the variable scopes for a given stackframe ID.
|
||||||
|
scopesRequest(response, args) {
|
||||||
|
this._runtime.scopesRequest(args, response, (resp, data) => {
|
||||||
|
// data is a JSON object
|
||||||
|
const s = data.body.scopes.map(l => {
|
||||||
|
const scope = new vscode_debugadapter.Scope(l.name, l.variablesReference);
|
||||||
|
scope.expensive = l.expensive;
|
||||||
|
return scope;
|
||||||
|
});
|
||||||
|
resp.body = {
|
||||||
|
scopes: s // The scopes of the stackframe. If the array has length zero, there are no scopes available.
|
||||||
|
};
|
||||||
|
this.sendResponse(resp);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stackTraceRequest(response, args) {
|
||||||
|
this._runtime.stackTraceRequest(args, response, (resp, data) => {
|
||||||
|
// data is a JSON object
|
||||||
|
const frames = data.body.stackFrames.map(f => {
|
||||||
|
const frame = new vscode_debugadapter.StackFrame(f.id, f.name, this.createSource(f.file), this.convertDebuggerLineToClient(f.line));
|
||||||
|
return frame;
|
||||||
|
});
|
||||||
|
resp.body = {
|
||||||
|
stackFrames: frames,
|
||||||
|
totalFrames: frames.count
|
||||||
|
};
|
||||||
|
this.sendResponse(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restartRequest(response, args) {
|
||||||
|
this._runtime.quit();
|
||||||
|
this.terminateSession('restart');
|
||||||
|
this.spawnBackend(this._args);
|
||||||
|
this._runtime.start(this._args);
|
||||||
|
this.sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't support multi-thread debugging, simply answer static TopJSDebugSession.THREAD_ID
|
||||||
|
threadsRequest(response, args) {
|
||||||
|
response.body = {
|
||||||
|
threads: [
|
||||||
|
new vscode_debugadapter.Thread(TopJSDebugSession.THREAD_ID, "thread 1")
|
||||||
|
]
|
||||||
|
};
|
||||||
|
this.sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// low priority
|
||||||
|
// TODO: implement this
|
||||||
|
evaluateRequest(response, args) {
|
||||||
|
// this._runtime.evaluateRequest(args)
|
||||||
|
response.body = {
|
||||||
|
result: undefined,
|
||||||
|
variablesReference: 0
|
||||||
|
};
|
||||||
|
this.sendResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't support multi-thread debugging, so hard code to 1
|
||||||
|
TopJSDebugSession.THREAD_ID = 1;
|
||||||
|
|
||||||
|
exports.TopJSDebugSession = TopJSDebugSession;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var awaiter = function(self, args, promise, generator) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
|
||||||
|
function fulfilled(value) {
|
||||||
|
try {
|
||||||
|
StereoPannerNode(generator.next(value));
|
||||||
|
} catch(e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rejected(value) {
|
||||||
|
try {
|
||||||
|
step(generator['throw'](value));
|
||||||
|
} catch(e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function step(result) {
|
||||||
|
if(result.done) {
|
||||||
|
resolve(result.value);
|
||||||
|
} else {
|
||||||
|
new Promise(function(resolve) {
|
||||||
|
resolve(result.value);
|
||||||
|
}).then(fulfilled, rejected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step((generator = generator.apply(self, args || [])).next());
|
||||||
|
});
|
||||||
|
}
|
334
src/topjsRuntime.js
Normal file
334
src/topjsRuntime.js
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
const events = require('events');
|
||||||
|
const net = require('net');
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
class dEvents { }
|
||||||
|
// Initialized Event
|
||||||
|
//initialize = "initialized";
|
||||||
|
|
||||||
|
// Stopped Event
|
||||||
|
// stopOnStep = "stop on step";
|
||||||
|
// stopOnBreakpoint = "stop on breakpoint"; // including: breakpoint, function breakpoint, data breakpoint
|
||||||
|
// stopOnException = "stop on exception";
|
||||||
|
// stopOnPause = "stop on pause";
|
||||||
|
// stopOnEntry = "stop on entry";
|
||||||
|
// stopOnGoto = "stop on goto"; // don't support??
|
||||||
|
// gather to one, call by emit(dEvent.stop, 'step')
|
||||||
|
dEvents.stopped = "stopped";
|
||||||
|
|
||||||
|
// Continued Event
|
||||||
|
dEvents.continued = "continued";
|
||||||
|
|
||||||
|
// Exited Event
|
||||||
|
// no implementation in vscode-debugadapter
|
||||||
|
//exited = "exited";
|
||||||
|
|
||||||
|
// Terminated Event
|
||||||
|
dEvents.terminated = "terminated";
|
||||||
|
|
||||||
|
// Thread Event
|
||||||
|
//thread = "thread"; // don't support
|
||||||
|
|
||||||
|
// Output event
|
||||||
|
dEvents.output = "output";
|
||||||
|
// stdoutOutput = "stdout output";
|
||||||
|
// stderrOutput = "stderr output";
|
||||||
|
// telemetryOutput = "telemetry output";
|
||||||
|
|
||||||
|
// Breakpoint Event
|
||||||
|
// breakpointChanged = "breakpoint changed";
|
||||||
|
// breakpointNew = "breakpoint new";
|
||||||
|
// breakpointRemoved = "breakpoint removed";
|
||||||
|
// gather to one, call by emit(dEvent.breakpoint, 'changed')
|
||||||
|
dEvents.breakpoint = "breakpoint";
|
||||||
|
|
||||||
|
// Module Event
|
||||||
|
// don't support
|
||||||
|
|
||||||
|
// LoadedSource Event
|
||||||
|
// don't support
|
||||||
|
// sourceNew = "source new";
|
||||||
|
// sourceChanged = "source changed";
|
||||||
|
// sourceRemoved = "source removed";
|
||||||
|
// gather to one, call by emit(dEvent.source, 'new')
|
||||||
|
//source = "source";
|
||||||
|
|
||||||
|
// Process Event
|
||||||
|
// don't support
|
||||||
|
// The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js.
|
||||||
|
//process = "process";
|
||||||
|
|
||||||
|
// custom event
|
||||||
|
dEvents.end = "end";
|
||||||
|
|
||||||
|
class dCommand { }
|
||||||
|
dCommand.initialize = "initialize";
|
||||||
|
dCommand.launch = "launch";
|
||||||
|
dCommand.attach = "attach";
|
||||||
|
dCommand.disconnect = "disconnect";
|
||||||
|
dCommand.terminate = "terminate";
|
||||||
|
dCommand.restart = "restart";
|
||||||
|
dCommand.setbreakpoints = "setBreakpoints";
|
||||||
|
dCommand.setfunctionbreakpoint = "setFunctionBreakpoints";
|
||||||
|
dCommand.configurationdone = "configurationDone";
|
||||||
|
dCommand.continue = "continue";
|
||||||
|
dCommand.next = "next";
|
||||||
|
dCommand.stepin = "stepIn";
|
||||||
|
dCommand.stepout = "stepOut";
|
||||||
|
// how could this possible ???
|
||||||
|
//stepback = "stepBack";
|
||||||
|
// reversecontinue = "reverseContinue";
|
||||||
|
// restartframe = "restartFrame";
|
||||||
|
// goto = "goto";
|
||||||
|
dCommand.pause = "pause";
|
||||||
|
dCommand.stackstrace = "stackTrace";
|
||||||
|
// what it mean???
|
||||||
|
dCommand.scopes = "scopes";
|
||||||
|
dCommand.variables = "variables";
|
||||||
|
// we don't support this action at this moment
|
||||||
|
dCommand.setvariable = "setVariable";
|
||||||
|
// ????
|
||||||
|
dCommand.setexpression = "setExpression";
|
||||||
|
dCommand.source = "source";
|
||||||
|
dCommand.threads = "threads";
|
||||||
|
dCommand.terminatethreads = "terminateThreads";
|
||||||
|
dCommand.evaluate = "evaluate";
|
||||||
|
dCommand.stepintargets = "stepInTargets";
|
||||||
|
dCommand.complitions = "complitions";
|
||||||
|
dCommand.exceptioninfo = "exceptionInfo";
|
||||||
|
dCommand.loadedsources = "loadedSources";
|
||||||
|
dCommand.databreakpointinfo = "dataBreakpointInfo";
|
||||||
|
dCommand.setdatabreakpoints = "setDataBreakpoints";
|
||||||
|
// custom request...
|
||||||
|
|
||||||
|
|
||||||
|
/// runtime arguments is pass by `vscode-debugadapter.DebuggSession.launchRequest`
|
||||||
|
/// we can rewrite this function to get arguments.
|
||||||
|
class topjsRuntime extends events.EventEmitter {
|
||||||
|
/**
|
||||||
|
* runtime is the debugger frontend which receive event and request
|
||||||
|
* from debugg adapter and forward to debugger backend vice-versa.
|
||||||
|
*
|
||||||
|
* actually, there's only one tcp connection, which is for communicating
|
||||||
|
* to debugger backend, for communicating with debugger adapter is happened
|
||||||
|
* in memory. the runtime simply read request/event from adapter and send
|
||||||
|
* to backend without any modification.
|
||||||
|
*
|
||||||
|
* messages all contain `type`, `seq` fileds.
|
||||||
|
* for reqeusts, contain extra `command` and optional `aguments` fileds
|
||||||
|
* for events, contain extra `event` and optional `body` fields
|
||||||
|
* for responses, contain extra `request_seq`, `success`, `command` and optional `message`, `body` fields
|
||||||
|
*
|
||||||
|
* NOTE: sequence of meesage is a pitfall.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._seq = 0;
|
||||||
|
this._cb = {};
|
||||||
|
this._data = [];
|
||||||
|
this._sep = '\r\n\r\n';
|
||||||
|
this._sep_len = 4;
|
||||||
|
this._started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLogger(l) {
|
||||||
|
this._logger = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildRequest(cmd, arg) {
|
||||||
|
const data = JSON.stringify({
|
||||||
|
seq: this._seq,
|
||||||
|
type: 'request',
|
||||||
|
command: cmd,
|
||||||
|
arguments: arg
|
||||||
|
}, null, '\t');
|
||||||
|
return `Content-Length: ${data.length}\r\n\r\n${data}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decode data into array, if data is not complete, save to this._data
|
||||||
|
* this method assume that the sequence of data arrived is same to been sent.
|
||||||
|
*/
|
||||||
|
decodeResponse(buf) {
|
||||||
|
let offset = 0;
|
||||||
|
let res = [];
|
||||||
|
while(true) {
|
||||||
|
let idx = buf.indexOf(this._sep, offset);
|
||||||
|
if(idx == 0) {
|
||||||
|
throw 'invalid message';
|
||||||
|
}
|
||||||
|
if(idx < 0) {
|
||||||
|
this._data.push(buf.slice(offset));
|
||||||
|
break; // read more
|
||||||
|
}
|
||||||
|
let p = buf.slice(offset, idx).toString();
|
||||||
|
if(p.indexOf(':') < 0) {
|
||||||
|
throw 'invalid message';
|
||||||
|
}
|
||||||
|
let header = p.split(':');
|
||||||
|
if(header.length != 2) {
|
||||||
|
throw 'invalid message';
|
||||||
|
}
|
||||||
|
if(header[0].trim() !== 'Content-Length') {
|
||||||
|
throw 'invalid message';
|
||||||
|
}
|
||||||
|
let len = parseInt(header[1].trim());
|
||||||
|
if(Number.isNaN(len)) {
|
||||||
|
throw 'invalid content-length';
|
||||||
|
}
|
||||||
|
let end = idx + this._sep_len + len;
|
||||||
|
let body = buf.slice(idx + this._sep_len, end);
|
||||||
|
if(body.length < len) {
|
||||||
|
this._data.push(buf.slice(offset));
|
||||||
|
break; // read more
|
||||||
|
}
|
||||||
|
offset = end;
|
||||||
|
res.push(JSON.parse(body.toString()));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
isStarted() { return this._started; }
|
||||||
|
|
||||||
|
// args is active configuration in launch.json, the default value is set in package.json
|
||||||
|
start(args) {
|
||||||
|
if(!args.port) {
|
||||||
|
this._logger.err('no port applied, exit');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
//const remote = "192.168.2.145";
|
||||||
|
const local = "127.0.0.1";
|
||||||
|
this.client = net.createConnection({ port: args.port, host: local });
|
||||||
|
this.client.on('connect', () => {
|
||||||
|
this._started = true;
|
||||||
|
setImmediate(_ => {
|
||||||
|
this.emit('connected', undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.client.on('data', (data) => {
|
||||||
|
this.onData(data);
|
||||||
|
});
|
||||||
|
this.client.on('end', () => {
|
||||||
|
//this._logger.log('end');
|
||||||
|
});
|
||||||
|
this.client.on('error', (err) => {
|
||||||
|
if(this._started) {
|
||||||
|
this._logger.err(err.message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.client.disconnect();
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.client.on('close', (e) => {
|
||||||
|
// extension is prevented to call exit
|
||||||
|
//process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
write(data) {
|
||||||
|
this.client.write(data);
|
||||||
|
return this._seq++;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResponse(data) {
|
||||||
|
if(data.seq in this._cb) {
|
||||||
|
this._cb[data.seq](data);
|
||||||
|
delete this._cb[data.seq];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(data) {
|
||||||
|
setImmediate(_ => {
|
||||||
|
this.emit(data.event, data)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
request(cmd, args, response, cb) {
|
||||||
|
const data = this.buildRequest(cmd, args);
|
||||||
|
const id = this.write(data);
|
||||||
|
this._cb[id] = (json) => {
|
||||||
|
cb(response, json);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onData(data) {
|
||||||
|
this._data.push(data);
|
||||||
|
let buf = Buffer.concat(this._data);
|
||||||
|
this._data.splice(0, this._data.length);
|
||||||
|
try {
|
||||||
|
const r = this.decodeResponse(buf);
|
||||||
|
for (var i = 0; i < r.length; ++i) {
|
||||||
|
const j = r[i];
|
||||||
|
if (j.type === 'event') {
|
||||||
|
this.handleEvent(j);
|
||||||
|
} else if (j.type === 'response') {
|
||||||
|
this.handleResponse(j);
|
||||||
|
} else {
|
||||||
|
this._logger.err(`unknown response ${data}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
this._logger.err('exception: ', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quit() {
|
||||||
|
try {
|
||||||
|
this.client.disconnect();
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
this._started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBreakpointsRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.setbreakpoints, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
continueRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.continue, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.pause, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
stepInRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.stepin, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
stepOutRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.stepout, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.next, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
variablesRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.variables, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
scopesRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.scopes, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
stackTraceRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.stackstrace, args, response, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handled in session, since we don't support multi-thread debugging.
|
||||||
|
// threadsRequest(args, response, cb) {
|
||||||
|
// this.request(dCommand.threads, args, response, cb);
|
||||||
|
// }
|
||||||
|
|
||||||
|
evaluateRequest(args, response, cb) {
|
||||||
|
this.request(dCommand.evaluate, args, response, cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.topjsRuntime = topjsRuntime;
|
||||||
|
exports.dEvents = dEvents;
|
||||||
|
exports.dCommand = dCommand;
|
Loading…
Reference in New Issue
Block a user