Skip to content

Commit bf7ac92

Browse files
committed
move auto-attach into new built-in extension; fixes #53586
1 parent 076a754 commit bf7ac92

13 files changed

+650
-1
lines changed

.vscode/launch.json

+9
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@
236236
"VSCODE_DEV": "1",
237237
"VSCODE_CLI": "1"
238238
}
239+
},
240+
{
241+
"name": "Launch Built-in Extension",
242+
"type": "extensionHost",
243+
"request": "launch",
244+
"runtimeExecutable": "${execPath}",
245+
"args": [
246+
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
247+
]
239248
}
240249
],
241250
"compounds": [

build/builtInExtensions.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[
22
{
33
"name": "ms-vscode.node-debug",
4-
"version": "1.26.1",
4+
"version": "1.26.2",
55
"repo": "https://github.com/Microsoft/vscode-node-debug"
66
},
77
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
src/**
2+
tsconfig.json
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "debug-auto-launch",
3+
"displayName": "%displayName%",
4+
"description": "%description%",
5+
"version": "1.0.0",
6+
"publisher": "vscode",
7+
"engines": {
8+
"vscode": "^1.5.0"
9+
},
10+
"activationEvents": [
11+
"*"
12+
],
13+
"main": "./out/extension",
14+
"scripts": {
15+
"compile": "gulp compile-extension:debug-auto-launch",
16+
"watch": "gulp watch-extension:debug-auto-launch"
17+
},
18+
"contributes": {
19+
"configuration": {
20+
"title": "Node debug",
21+
"properties": {
22+
"debug.node.autoAttach": {
23+
"scope": "window",
24+
"type": "string",
25+
"enum": [
26+
"disabled",
27+
"on",
28+
"off"
29+
],
30+
"enumDescriptions": [
31+
"%debug.node.autoAttach.disabled.description%",
32+
"%debug.node.autoAttach.on.description%",
33+
"%debug.node.autoAttach.off.description%"
34+
],
35+
"description": "%debug.node.autoAttach.description%",
36+
"default": "disabled"
37+
}
38+
}
39+
},
40+
"commands": [
41+
{
42+
"command": "extension.node-debug.toggleAutoAttach",
43+
"title": "%toggle.auto.attach%",
44+
"category": "Debug"
45+
}
46+
]
47+
},
48+
"dependencies": {
49+
"vscode-nls": "^3.2.4"
50+
},
51+
"devDependencies": {
52+
"@types/node": "8.0.33"
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"displayName": "Node Debug Auto-attach",
3+
"description": "Helper for auto-attach feature when node-debug extensions are not active.",
4+
5+
"debug.node.autoAttach.description": "Automatically attach node debugger when node.js was launched in debug mode from integrated terminal.",
6+
"debug.node.autoAttach.disabled.description": "Auto attach is disabled and not shown in status bar.",
7+
"debug.node.autoAttach.on.description": "Auto attach is active.",
8+
"debug.node.autoAttach.off.description": "Auto attach is inactive.",
9+
10+
"toggle.auto.attach": "Toggle Auto Attach"
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import * as vscode from 'vscode';
9+
import * as nls from 'vscode-nls';
10+
import { basename } from 'path';
11+
import { pollProcesses, attachToProcess } from './nodeProcessTree';
12+
13+
const localize = nls.loadMessageBundle();
14+
15+
export function startAutoAttach(rootPid: number): vscode.Disposable {
16+
17+
return pollProcesses(rootPid, true, (pid, cmdPath, args) => {
18+
const cmdName = basename(cmdPath, '.exe');
19+
if (cmdName === 'node') {
20+
const name = localize('process.with.pid.label', "Process {0}", pid);
21+
attachToProcess(undefined, name, pid, args);
22+
}
23+
});
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import * as vscode from 'vscode';
9+
import * as nls from 'vscode-nls';
10+
import { basename } from 'path';
11+
import { pollProcesses, attachToProcess } from './nodeProcessTree';
12+
13+
const localize = nls.loadMessageBundle();
14+
const ON_TEXT = localize('status.text.auto.attach.on', "Auto Attach: On");
15+
const OFF_TEXT = localize('status.text.auto.attach.off', "Auto Attach: Off");
16+
17+
const TOGGLE_COMMAND = 'extension.node-debug.toggleAutoAttach';
18+
19+
let currentState: string;
20+
let autoAttacher: vscode.Disposable | undefined;
21+
let statusItem: vscode.StatusBarItem | undefined = undefined;
22+
23+
24+
export function activate(context: vscode.ExtensionContext): void {
25+
26+
context.subscriptions.push(vscode.commands.registerCommand(TOGGLE_COMMAND, toggleAutoAttach));
27+
28+
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
29+
if (e.affectsConfiguration('debug.node.autoAttach')) {
30+
updateAutoAttachInStatus(context);
31+
}
32+
}));
33+
34+
updateAutoAttachInStatus(context);
35+
}
36+
37+
export function deactivate(): void {
38+
}
39+
40+
41+
function toggleAutoAttach(context: vscode.ExtensionContext) {
42+
43+
const conf = vscode.workspace.getConfiguration('debug.node');
44+
45+
let value = conf.get('autoAttach');
46+
if (value === 'on') {
47+
value = 'off';
48+
} else {
49+
value = 'on';
50+
}
51+
52+
const info = conf.inspect('autoAttach');
53+
let target: vscode.ConfigurationTarget = vscode.ConfigurationTarget.Global;
54+
if (info) {
55+
if (info.workspaceFolderValue) {
56+
target = vscode.ConfigurationTarget.WorkspaceFolder;
57+
} else if (info.workspaceValue) {
58+
target = vscode.ConfigurationTarget.Workspace;
59+
} else if (info.globalValue) {
60+
target = vscode.ConfigurationTarget.Global;
61+
} else if (info.defaultValue) {
62+
// setting not yet used: store setting in workspace
63+
if (vscode.workspace.workspaceFolders) {
64+
target = vscode.ConfigurationTarget.Workspace;
65+
}
66+
}
67+
}
68+
conf.update('autoAttach', value, target);
69+
70+
updateAutoAttachInStatus(context);
71+
}
72+
73+
function updateAutoAttachInStatus(context: vscode.ExtensionContext) {
74+
75+
const newState = <string>vscode.workspace.getConfiguration('debug.node').get('autoAttach');
76+
77+
if (newState !== currentState) {
78+
79+
currentState = newState;
80+
81+
if (newState === 'disabled') {
82+
83+
// turn everything off
84+
if (statusItem) {
85+
statusItem.hide();
86+
statusItem.text = OFF_TEXT;
87+
}
88+
if (autoAttacher) {
89+
autoAttacher.dispose();
90+
autoAttacher = undefined;
91+
}
92+
93+
} else { // 'on' or 'off'
94+
95+
// make sure status bar item exists and is visible
96+
if (!statusItem) {
97+
statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
98+
statusItem.command = TOGGLE_COMMAND;
99+
statusItem.text = OFF_TEXT;
100+
statusItem.tooltip = localize('status.tooltip.auto.attach', "Automatically attach to node.js processes in debug mode");
101+
statusItem.show();
102+
context.subscriptions.push(statusItem);
103+
} else {
104+
statusItem.show();
105+
}
106+
107+
if (newState === 'off') {
108+
statusItem.text = OFF_TEXT;
109+
if (autoAttacher) {
110+
autoAttacher.dispose();
111+
autoAttacher = undefined;
112+
}
113+
} else if (newState === 'on') {
114+
statusItem.text = ON_TEXT;
115+
const vscode_pid = process.env['VSCODE_PID'];
116+
const rootPid = vscode_pid ? parseInt(vscode_pid) : 0;
117+
autoAttacher = startAutoAttach(rootPid);
118+
}
119+
}
120+
}
121+
}
122+
123+
function startAutoAttach(rootPid: number): vscode.Disposable {
124+
125+
return pollProcesses(rootPid, true, (pid, cmdPath, args) => {
126+
const cmdName = basename(cmdPath, '.exe');
127+
if (cmdName === 'node') {
128+
const name = localize('process.with.pid.label', "Process {0}", pid);
129+
attachToProcess(undefined, name, pid, args);
130+
}
131+
});
132+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import * as vscode from 'vscode';
9+
import { getProcessTree, ProcessTreeNode } from './processTree';
10+
import { analyseArguments } from './protocolDetection';
11+
12+
const pids = new Set<number>();
13+
14+
const POLL_INTERVAL = 1000;
15+
16+
/**
17+
* Poll for all subprocesses of given root process.
18+
*/
19+
export function pollProcesses(rootPid: number, inTerminal: boolean, cb: (pid: number, cmd: string, args: string) => void): vscode.Disposable {
20+
21+
let stopped = false;
22+
23+
function poll() {
24+
//const start = Date.now();
25+
findChildProcesses(rootPid, inTerminal, cb).then(_ => {
26+
//console.log(`duration: ${Date.now() - start}`);
27+
setTimeout(_ => {
28+
if (!stopped) {
29+
poll();
30+
}
31+
}, POLL_INTERVAL);
32+
});
33+
}
34+
35+
poll();
36+
37+
return new vscode.Disposable(() => stopped = true);
38+
}
39+
40+
export function attachToProcess(folder: vscode.WorkspaceFolder | undefined, name: string, pid: number, args: string, baseConfig?: vscode.DebugConfiguration) {
41+
42+
if (pids.has(pid)) {
43+
return;
44+
}
45+
pids.add(pid);
46+
47+
const config: vscode.DebugConfiguration = {
48+
type: 'node',
49+
request: 'attach',
50+
name: name,
51+
stopOnEntry: false
52+
};
53+
54+
if (baseConfig) {
55+
// selectively copy attributes
56+
if (baseConfig.timeout) {
57+
config.timeout = baseConfig.timeout;
58+
}
59+
if (baseConfig.sourceMaps) {
60+
config.sourceMaps = baseConfig.sourceMaps;
61+
}
62+
if (baseConfig.outFiles) {
63+
config.outFiles = baseConfig.outFiles;
64+
}
65+
if (baseConfig.sourceMapPathOverrides) {
66+
config.sourceMapPathOverrides = baseConfig.sourceMapPathOverrides;
67+
}
68+
if (baseConfig.smartStep) {
69+
config.smartStep = baseConfig.smartStep;
70+
}
71+
if (baseConfig.skipFiles) {
72+
config.skipFiles = baseConfig.skipFiles;
73+
}
74+
if (baseConfig.showAsyncStacks) {
75+
config.sourceMaps = baseConfig.showAsyncStacks;
76+
}
77+
if (baseConfig.trace) {
78+
config.trace = baseConfig.trace;
79+
}
80+
}
81+
82+
let { usePort, protocol, port } = analyseArguments(args);
83+
if (usePort) {
84+
config.processId = `${protocol}${port}`;
85+
} else {
86+
if (protocol && port > 0) {
87+
config.processId = `${pid}${protocol}${port}`;
88+
} else {
89+
config.processId = pid.toString();
90+
}
91+
}
92+
93+
vscode.debug.startDebugging(folder, config);
94+
}
95+
96+
function findChildProcesses(rootPid: number, inTerminal: boolean, cb: (pid: number, cmd: string, args: string) => void): Promise<void> {
97+
98+
function walker(node: ProcessTreeNode, terminal: boolean, renderer: number) {
99+
100+
if (node.args.indexOf('--type=terminal') >= 0 && (renderer === 0 || node.ppid === renderer)) {
101+
terminal = true;
102+
}
103+
104+
let { protocol } = analyseArguments(node.args);
105+
if (terminal && protocol) {
106+
cb(node.pid, node.command, node.args);
107+
}
108+
109+
for (const child of node.children || []) {
110+
walker(child, terminal, renderer);
111+
}
112+
}
113+
114+
function finder(node: ProcessTreeNode, pid: number): ProcessTreeNode | undefined {
115+
if (node.pid === pid) {
116+
return node;
117+
}
118+
for (const child of node.children || []) {
119+
const p = finder(child, pid);
120+
if (p) {
121+
return p;
122+
}
123+
}
124+
return undefined;
125+
}
126+
127+
return getProcessTree(rootPid).then(tree => {
128+
if (tree) {
129+
130+
// find the pid of the renderer process
131+
const extensionHost = finder(tree, process.pid);
132+
let rendererPid = extensionHost ? extensionHost.ppid : 0;
133+
134+
for (const child of tree.children || []) {
135+
walker(child, !inTerminal, rendererPid);
136+
}
137+
}
138+
});
139+
}

0 commit comments

Comments
 (0)