Skip to content

Commit

Permalink
move auto-attach into new built-in extension; fixes microsoft#53586
Browse files Browse the repository at this point in the history
  • Loading branch information
weinand committed Jul 12, 2018
1 parent 076a754 commit bf7ac92
Show file tree
Hide file tree
Showing 13 changed files with 650 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@
"VSCODE_DEV": "1",
"VSCODE_CLI": "1"
}
},
{
"name": "Launch Built-in Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
]
}
],
"compounds": [
Expand Down
2 changes: 1 addition & 1 deletion build/builtInExtensions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"name": "ms-vscode.node-debug",
"version": "1.26.1",
"version": "1.26.2",
"repo": "https://github.com/Microsoft/vscode-node-debug"
},
{
Expand Down
2 changes: 2 additions & 0 deletions extensions/debug-auto-launch/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/**
tsconfig.json
54 changes: 54 additions & 0 deletions extensions/debug-auto-launch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "debug-auto-launch",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.0.0",
"publisher": "vscode",
"engines": {
"vscode": "^1.5.0"
},
"activationEvents": [
"*"
],
"main": "./out/extension",
"scripts": {
"compile": "gulp compile-extension:debug-auto-launch",
"watch": "gulp watch-extension:debug-auto-launch"
},
"contributes": {
"configuration": {
"title": "Node debug",
"properties": {
"debug.node.autoAttach": {
"scope": "window",
"type": "string",
"enum": [
"disabled",
"on",
"off"
],
"enumDescriptions": [
"%debug.node.autoAttach.disabled.description%",
"%debug.node.autoAttach.on.description%",
"%debug.node.autoAttach.off.description%"
],
"description": "%debug.node.autoAttach.description%",
"default": "disabled"
}
}
},
"commands": [
{
"command": "extension.node-debug.toggleAutoAttach",
"title": "%toggle.auto.attach%",
"category": "Debug"
}
]
},
"dependencies": {
"vscode-nls": "^3.2.4"
},
"devDependencies": {
"@types/node": "8.0.33"
}
}
11 changes: 11 additions & 0 deletions extensions/debug-auto-launch/package.nls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"displayName": "Node Debug Auto-attach",
"description": "Helper for auto-attach feature when node-debug extensions are not active.",

"debug.node.autoAttach.description": "Automatically attach node debugger when node.js was launched in debug mode from integrated terminal.",
"debug.node.autoAttach.disabled.description": "Auto attach is disabled and not shown in status bar.",
"debug.node.autoAttach.on.description": "Auto attach is active.",
"debug.node.autoAttach.off.description": "Auto attach is inactive.",

"toggle.auto.attach": "Toggle Auto Attach"
}
24 changes: 24 additions & 0 deletions extensions/debug-auto-launch/src/autoAttach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { basename } from 'path';
import { pollProcesses, attachToProcess } from './nodeProcessTree';

const localize = nls.loadMessageBundle();

export function startAutoAttach(rootPid: number): vscode.Disposable {

return pollProcesses(rootPid, true, (pid, cmdPath, args) => {
const cmdName = basename(cmdPath, '.exe');
if (cmdName === 'node') {
const name = localize('process.with.pid.label', "Process {0}", pid);
attachToProcess(undefined, name, pid, args);
}
});
}
132 changes: 132 additions & 0 deletions extensions/debug-auto-launch/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { basename } from 'path';
import { pollProcesses, attachToProcess } from './nodeProcessTree';

const localize = nls.loadMessageBundle();
const ON_TEXT = localize('status.text.auto.attach.on', "Auto Attach: On");
const OFF_TEXT = localize('status.text.auto.attach.off', "Auto Attach: Off");

const TOGGLE_COMMAND = 'extension.node-debug.toggleAutoAttach';

let currentState: string;
let autoAttacher: vscode.Disposable | undefined;
let statusItem: vscode.StatusBarItem | undefined = undefined;


export function activate(context: vscode.ExtensionContext): void {

context.subscriptions.push(vscode.commands.registerCommand(TOGGLE_COMMAND, toggleAutoAttach));

context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('debug.node.autoAttach')) {
updateAutoAttachInStatus(context);
}
}));

updateAutoAttachInStatus(context);
}

export function deactivate(): void {
}


function toggleAutoAttach(context: vscode.ExtensionContext) {

const conf = vscode.workspace.getConfiguration('debug.node');

let value = conf.get('autoAttach');
if (value === 'on') {
value = 'off';
} else {
value = 'on';
}

const info = conf.inspect('autoAttach');
let target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Global;
if (info) {
if (info.workspaceFolderValue) {
target = vscode.ConfigurationTarget.WorkspaceFolder;
} else if (info.workspaceValue) {
target = vscode.ConfigurationTarget.Workspace;
} else if (info.globalValue) {
target = vscode.ConfigurationTarget.Global;
} else if (info.defaultValue) {
// setting not yet used: store setting in workspace
if (vscode.workspace.workspaceFolders) {
target = vscode.ConfigurationTarget.Workspace;
}
}
}
conf.update('autoAttach', value, target);

updateAutoAttachInStatus(context);
}

function updateAutoAttachInStatus(context: vscode.ExtensionContext) {

const newState = <string>vscode.workspace.getConfiguration('debug.node').get('autoAttach');

if (newState !== currentState) {

currentState = newState;

if (newState === 'disabled') {

// turn everything off
if (statusItem) {
statusItem.hide();
statusItem.text = OFF_TEXT;
}
if (autoAttacher) {
autoAttacher.dispose();
autoAttacher = undefined;
}

} else { // 'on' or 'off'

// make sure status bar item exists and is visible
if (!statusItem) {
statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
statusItem.command = TOGGLE_COMMAND;
statusItem.text = OFF_TEXT;
statusItem.tooltip = localize('status.tooltip.auto.attach', "Automatically attach to node.js processes in debug mode");
statusItem.show();
context.subscriptions.push(statusItem);
} else {
statusItem.show();
}

if (newState === 'off') {
statusItem.text = OFF_TEXT;
if (autoAttacher) {
autoAttacher.dispose();
autoAttacher = undefined;
}
} else if (newState === 'on') {
statusItem.text = ON_TEXT;
const vscode_pid = process.env['VSCODE_PID'];
const rootPid = vscode_pid ? parseInt(vscode_pid) : 0;
autoAttacher = startAutoAttach(rootPid);
}
}
}
}

function startAutoAttach(rootPid: number): vscode.Disposable {

return pollProcesses(rootPid, true, (pid, cmdPath, args) => {
const cmdName = basename(cmdPath, '.exe');
if (cmdName === 'node') {
const name = localize('process.with.pid.label', "Process {0}", pid);
attachToProcess(undefined, name, pid, args);
}
});
}
139 changes: 139 additions & 0 deletions extensions/debug-auto-launch/src/nodeProcessTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

import * as vscode from 'vscode';
import { getProcessTree, ProcessTreeNode } from './processTree';
import { analyseArguments } from './protocolDetection';

const pids = new Set<number>();

const POLL_INTERVAL = 1000;

/**
* Poll for all subprocesses of given root process.
*/
export function pollProcesses(rootPid: number, inTerminal: boolean, cb: (pid: number, cmd: string, args: string) => void): vscode.Disposable {

let stopped = false;

function poll() {
//const start = Date.now();
findChildProcesses(rootPid, inTerminal, cb).then(_ => {
//console.log(`duration: ${Date.now() - start}`);
setTimeout(_ => {
if (!stopped) {
poll();
}
}, POLL_INTERVAL);
});
}

poll();

return new vscode.Disposable(() => stopped = true);
}

export function attachToProcess(folder: vscode.WorkspaceFolder | undefined, name: string, pid: number, args: string, baseConfig?: vscode.DebugConfiguration) {

if (pids.has(pid)) {
return;
}
pids.add(pid);

const config: vscode.DebugConfiguration = {
type: 'node',
request: 'attach',
name: name,
stopOnEntry: false
};

if (baseConfig) {
// selectively copy attributes
if (baseConfig.timeout) {
config.timeout = baseConfig.timeout;
}
if (baseConfig.sourceMaps) {
config.sourceMaps = baseConfig.sourceMaps;
}
if (baseConfig.outFiles) {
config.outFiles = baseConfig.outFiles;
}
if (baseConfig.sourceMapPathOverrides) {
config.sourceMapPathOverrides = baseConfig.sourceMapPathOverrides;
}
if (baseConfig.smartStep) {
config.smartStep = baseConfig.smartStep;
}
if (baseConfig.skipFiles) {
config.skipFiles = baseConfig.skipFiles;
}
if (baseConfig.showAsyncStacks) {
config.sourceMaps = baseConfig.showAsyncStacks;
}
if (baseConfig.trace) {
config.trace = baseConfig.trace;
}
}

let { usePort, protocol, port } = analyseArguments(args);
if (usePort) {
config.processId = `${protocol}${port}`;
} else {
if (protocol && port > 0) {
config.processId = `${pid}${protocol}${port}`;
} else {
config.processId = pid.toString();
}
}

vscode.debug.startDebugging(folder, config);
}

function findChildProcesses(rootPid: number, inTerminal: boolean, cb: (pid: number, cmd: string, args: string) => void): Promise<void> {

function walker(node: ProcessTreeNode, terminal: boolean, renderer: number) {

if (node.args.indexOf('--type=terminal') >= 0 && (renderer === 0 || node.ppid === renderer)) {
terminal = true;
}

let { protocol } = analyseArguments(node.args);
if (terminal && protocol) {
cb(node.pid, node.command, node.args);
}

for (const child of node.children || []) {
walker(child, terminal, renderer);
}
}

function finder(node: ProcessTreeNode, pid: number): ProcessTreeNode | undefined {
if (node.pid === pid) {
return node;
}
for (const child of node.children || []) {
const p = finder(child, pid);
if (p) {
return p;
}
}
return undefined;
}

return getProcessTree(rootPid).then(tree => {
if (tree) {

// find the pid of the renderer process
const extensionHost = finder(tree, process.pid);
let rendererPid = extensionHost ? extensionHost.ppid : 0;

for (const child of tree.children || []) {
walker(child, !inTerminal, rendererPid);
}
}
});
}
Loading

0 comments on commit bf7ac92

Please sign in to comment.