Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
process picker overhaul for microsoft/vscode/issues/42521
Browse files Browse the repository at this point in the history
  • Loading branch information
weinand committed Mar 17, 2018
1 parent 8721a2c commit 0013a0d
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 206 deletions.
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@
"activationEvents": [
"onDebugInitialConfigurations",
"onDebugResolve:node",
"onCommand:extension.pickNodeProcess",
"onCommand:extension.node-debug.toggleSkippingFile",
"onCommand:extension.node-debug.pickLoadedScript",
"onCommand:extension.pickNodeProcess",
"onCommand:extension.node-debug.toggleAutoAttach"
"onCommand:extension.node-debug.toggleAutoAttach",
"onCommand:extension.node-debug.attachNodeProcess"
],
"contributes": {
"views": {
Expand All @@ -111,6 +112,11 @@
"title": "%open.loaded.script%",
"category": "Debug"
},
{
"command": "extension.node-debug.attachNodeProcess",
"title": "%attach.node.process%",
"category": "Debug"
},
{
"command": "extension.node-debug.toggleSkippingFile",
"title": "%toggle.skipping.this.file%"
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"node.label": "Node.js",

"open.loaded.script": "Open Loaded Script",
"attach.node.process": "Attach to Node Process",
"toggle.skipping.this.file": "Toggle Skipping this File",

"loaded.scripts.view.name": "Loaded Scripts",
Expand Down
6 changes: 2 additions & 4 deletions src/node/extension/autoAttach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ import { pollProcesses, attachToProcess } from './nodeProcessTree';

const localize = nls.loadMessageBundle();

export function startAutoAttach() : vscode.Disposable {

const rootPid = parseInt(process.env['VSCODE_PID']);
export function startAutoAttach(rootPid: number) : vscode.Disposable {

return pollProcesses(rootPid, (pid, cmd) => {
if (cmd.indexOf('node ') >= 0) {
const name = localize('processWithPid', "Process {0}", pid);
const name = localize('process.with.pid.label', "Process {0}", pid);
attachToProcess(undefined, name, pid, cmd);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,36 @@ import { pollProcesses, attachToProcess } from './nodeProcessTree';

const localize = nls.loadMessageBundle();

const clusters = new Map<string,Cluster>();
export class Cluster {

export function prepareAutoAttachChildProcesses(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) {
clusters.set(config.name, new Cluster(folder, config));
}
static clusters = new Map<string,Cluster>();

export function startSession(session: vscode.DebugSession) {
const cluster = clusters.get(session.name);
if (cluster) {
cluster.startWatching(session);
}
}
private poller?: vscode.Disposable;

export function stopSession(session: vscode.DebugSession) {
const cluster = clusters.get(session.name);
if (cluster) {
cluster.stopWatching();
clusters.delete(session.name);

public static prepareAutoAttachChildProcesses(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) {
this.clusters.set(config.name, new Cluster(folder, config));
}
}

//---- private
static startSession(session: vscode.DebugSession) {
const cluster = this.clusters.get(session.name);
if (cluster) {
cluster.startWatching(session);
}
}

class Cluster {
folder: vscode.WorkspaceFolder | undefined;
config: vscode.DebugConfiguration;
poller?: vscode.Disposable;
static stopSession(session: vscode.DebugSession) {
const cluster = this.clusters.get(session.name);
if (cluster) {
cluster.stopWatching();
this.clusters.delete(session.name);
}
}

constructor(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) {
this.folder = folder;
this.config = config;
private constructor(private _folder: vscode.WorkspaceFolder | undefined, private _config: vscode.DebugConfiguration) {
}

startWatching(session: vscode.DebugSession) {
private startWatching(session: vscode.DebugSession) {

setTimeout(_ => {
// get the process ID from the debuggee
Expand All @@ -60,7 +56,7 @@ class Cluster {
}, session.type === 'node2' ? 500 : 100);
}

stopWatching() {
private stopWatching() {
if (this.poller) {
this.poller.dispose();
this.poller = undefined;
Expand All @@ -69,8 +65,8 @@ class Cluster {

private attachChildProcesses(rootPid: number) {
this.poller = pollProcesses(rootPid, (pid, cmd) => {
const name = localize('childProcessWithPid', "Child Process {0}", pid);
attachToProcess(this.folder, name, pid, cmd, this.config);
const name = localize('child.process.with.pid.label', "Child Process {0}", pid);
attachToProcess(this._folder, name, pid, cmd, this._config);
});
}
}
97 changes: 13 additions & 84 deletions src/node/extension/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@

import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { execSync } from 'child_process';
import { join, isAbsolute, dirname } from 'path';
import * as fs from 'fs';

import { writeToConsole } from './utilities';
import { detectDebugType, detectProtocolForPid, INSPECTOR_PORT_DEFAULT, LEGACY_PORT_DEFAULT } from './protocolDetection';
import { pickProcess } from './processPicker';
import { prepareAutoAttachChildProcesses } from './childProcesses';
import { detectDebugType } from './protocolDetection';
import { pickProcessForConfig } from './processPicker';
import { Cluster } from './cluster';

const localize = nls.loadMessageBundle();

//---- NodeConfigurationProvider

export class NodeConfigurationProvider implements vscode.DebugConfigurationProvider {

constructor(
private _extensionContext: vscode.ExtensionContext
) { }
constructor(private _extensionContext: vscode.ExtensionContext) {
}

/**
* Returns an initial debug configuration based on contextual information, e.g. package.json or folder.
Expand Down Expand Up @@ -103,14 +101,17 @@ export class NodeConfigurationProvider implements vscode.DebugConfigurationProvi

// "auto attach child process" support
if (config.autoAttachChildProcesses) {
prepareAutoAttachChildProcesses(folder, config);
Cluster.prepareAutoAttachChildProcesses(folder, config);
}

// "attach to process via pid" support
// "attach to process via picker" support
if (config.request === 'attach' && typeof config.processId === 'string') {
// we resolve Process Picker early (before VS Code) so that we can probe the process for its protocol here
if (await this.resolveProcessPicker(config)) {
return undefined; // abort launch
// we resolve Process Picker early (before VS Code) so that we can probe the process for its protocol
const processId = config.processId.trim();
if (processId === '${command:PickProcess}' || processId === '${command:extension.pickNodeProcess}') {
if (await pickProcessForConfig(config)) {
return undefined; // abort launch
}
}
}

Expand All @@ -130,42 +131,6 @@ export class NodeConfigurationProvider implements vscode.DebugConfigurationProvi
return config;
}

/**
* returns true if UI was cancelled
*/
private async resolveProcessPicker(config: vscode.DebugConfiguration) : Promise<boolean> {

const processId = config.processId.trim();
if (processId === '${command:PickProcess}' || processId === '${command:extension.pickNodeProcess}') {

const pidResult = await pickProcess();
if (pidResult === null) {
// UI dismissed (cancelled)
return true;
}

if (pidResult && pidResult.match(/^[0-9]+$/)) {
const pid = Number(pidResult);

putPidInDebugMode(pid);

const debugType = await determineDebugTypeForPidInDebugMode(config, pid);
if (debugType) {
// processID is handled, so turn this config into a normal port attach configuration
delete config.processId;
config.port = debugType === 'node2' ? INSPECTOR_PORT_DEFAULT : LEGACY_PORT_DEFAULT;
config.protocol = debugType === 'node2' ? 'inspector' : 'legacy';
} else {
throw new Error(localize('pid.error', "Attach to process: cannot put process '{0}' in debug mode.", pidResult));
}
} else {
throw new Error(localize('VSND2006', "Attach to process: '{0}' doesn't look like a process id.", pidResult));
}
}

return false;
}

/**
* if a runtime version is specified we prepend env.PATH with the folder that corresponds to the version.
* Returns false on error
Expand Down Expand Up @@ -408,42 +373,6 @@ function determineDebugType(config: any, logger: vscode.Logger): Promise<string
}
}

function putPidInDebugMode(pid: number): void {
try {
if (process.platform === 'win32') {
// regular node has an undocumented API function for forcing another node process into debug mode.
// (<any>process)._debugProcess(pid);
// But since we are running on Electron's node, process._debugProcess doesn't work (for unknown reasons).
// So we use a regular node instead:
const command = `node -e process._debugProcess(${pid})`;
execSync(command);
} else {
process.kill(pid, 'SIGUSR1');
}
} catch (e) {
throw new Error(localize('VSND2021', "Attach to process: cannot enable debug mode for process '{0}' ({1}).", pid, e));
}
}

function determineDebugTypeForPidInDebugMode(config: any, pid: number): Promise<string | null> {
let debugProtocolP: Promise<string | null>;
if (config.port === INSPECTOR_PORT_DEFAULT) {
debugProtocolP = Promise.resolve('inspector');
} else if (config.port === LEGACY_PORT_DEFAULT) {
debugProtocolP = Promise.resolve('legacy');
} else if (config.protocol) {
debugProtocolP = Promise.resolve(config.protocol);
} else {
debugProtocolP = detectProtocolForPid(pid);
}

return debugProtocolP.then(debugProtocol => {
return debugProtocol === 'inspector' ? 'node2' :
debugProtocol === 'legacy' ? 'node' :
null;
});
}

function nvsStandardArchName(arch) {
switch (arch) {
case '32':
Expand Down
20 changes: 12 additions & 8 deletions src/node/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import * as vscode from 'vscode';

import { NodeConfigurationProvider } from './configurationProvider';
import { LoadedScriptsProvider, pickLoadedScript, openScript } from './loadedScripts';
import { pickProcess } from './processPicker';
import { startSession, stopSession } from './childProcesses';
import { pickProcess, attachProcess } from './processPicker';
import { Cluster } from './cluster';
import { startAutoAttach } from './autoAttach';
import * as nls from 'vscode-nls';

Expand All @@ -23,17 +23,20 @@ export function activate(context: vscode.ExtensionContext) {
// toggle skipping file action
context.subscriptions.push(vscode.commands.registerCommand('extension.node-debug.toggleSkippingFile', toggleSkippingFile));

// process quickpicker
context.subscriptions.push(vscode.commands.registerCommand('extension.pickNodeProcess', () => pickProcess()));
// process picker command
context.subscriptions.push(vscode.commands.registerCommand('extension.pickNodeProcess', pickProcess));

// attach process command
context.subscriptions.push(vscode.commands.registerCommand('extension.node-debug.attachNodeProcess', attachProcess));

// loaded scripts
vscode.window.registerTreeDataProvider('extension.node-debug.loadedScriptsExplorer', new LoadedScriptsProvider(context));
context.subscriptions.push(vscode.commands.registerCommand('extension.node-debug.pickLoadedScript', () => pickLoadedScript()));
context.subscriptions.push(vscode.commands.registerCommand('extension.node-debug.pickLoadedScript', pickLoadedScript));
context.subscriptions.push(vscode.commands.registerCommand('extension.node-debug.openScript', (session: vscode.DebugSession, source) => openScript(session, source)));

// cluster
context.subscriptions.push(vscode.debug.onDidStartDebugSession(session => startSession(session)));
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(session => stopSession(session)));
context.subscriptions.push(vscode.debug.onDidStartDebugSession(session => Cluster.startSession(session)));
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(session => Cluster.stopSession(session)));

// auto attach in terminal
const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
Expand All @@ -43,6 +46,7 @@ export function activate(context: vscode.ExtensionContext) {
statusItem.show();
context.subscriptions.push(statusItem);

const rootPid = parseInt(process.env['VSCODE_PID']);
let autoAttacher: vscode.Disposable | undefined;
context.subscriptions.push(vscode.commands.registerCommand('extension.node-debug.toggleAutoAttach', _ => {
const icon = '$(plug) ';
Expand All @@ -52,7 +56,7 @@ export function activate(context: vscode.ExtensionContext) {
autoAttacher = undefined;
} else {
statusItem.text = icon + statusItem.text;
autoAttacher = startAutoAttach();
autoAttacher = startAutoAttach(rootPid);
}
}));
}
Expand Down
Loading

0 comments on commit 0013a0d

Please sign in to comment.