Skip to content

Commit

Permalink
feat: dynamically provide launch configurations
Browse files Browse the repository at this point in the history
Debugs npm scripts and the current file.

Fixes #433
Fixes microsoft/vscode#88230
  • Loading branch information
connor4312 committed Apr 27, 2020
1 parent e6feeed commit c41ec8c
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 25 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"enableProposedApi": true,
"activationEvents": [
"onLanguage:json",
"onDebugDynamicConfigurations",
"onDebugInitialConfigurations",
"onDebugResolve:node",
"onDebugResolve:extensionHost",
Expand Down
1 change: 1 addition & 0 deletions src/typings/json.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '*.json';
5 changes: 5 additions & 0 deletions src/ui/configuration/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*--------------------------------------------------------*/

import * as vscode from 'vscode';
import { contributes } from '../../../package.json';

// Here we create separate sets of interfaces for providing and resolving
// debug configuration.
Expand Down Expand Up @@ -43,3 +44,7 @@ export interface IDebugConfigurationProvider {
}

export const IDebugConfigurationProvider = Symbol('IDebugConfigurationProvider');

export const breakpointLanguages: ReadonlyArray<string> = contributes.breakpoints.map(
(b: { language: string }) => b.language,
);
8 changes: 6 additions & 2 deletions src/ui/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
import { ExtensionHostConfigurationResolver } from './extensionHostConfigurationProvider';
import { NodeConfigurationResolver } from './nodeDebugConfigurationResolver';
import { TerminalDebugConfigurationResolver } from './terminalDebugConfigurationResolver';
import { NodeDebugConfigurationProvider } from './nodeDebugConfigurationProvider';
import {
NodeInitialDebugConfigurationProvider,
NodeDynamicDebugConfigurationProvider,
} from './nodeDebugConfigurationProvider';

export const allConfigurationResolvers = [
ChromeDebugConfigurationResolver,
Expand All @@ -27,5 +30,6 @@ export const allConfigurationResolvers = [
export const allConfigurationProviders = [
ChromeDebugConfigurationProvider,
EdgeDebugConfigurationProvider,
NodeDebugConfigurationProvider,
NodeInitialDebugConfigurationProvider,
NodeDynamicDebugConfigurationProvider,
];
80 changes: 78 additions & 2 deletions src/ui/configuration/nodeDebugConfigurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { injectable } from 'inversify';
import { DebugType } from '../../common/contributionUtils';
import { createLaunchConfigFromContext } from './nodeDebugConfigurationResolver';
import { BaseConfigurationProvider } from './baseConfigurationProvider';
import { AnyNodeConfiguration } from '../../configuration';
import {
AnyNodeConfiguration,
ResolvingNodeLaunchConfiguration,
AnyResolvingConfiguration,
AnyTerminalConfiguration,
} from '../../configuration';
import { breakpointLanguages } from '.';
import { findScripts } from '../debugNpmScript';
import { flatten } from '../../common/objUtils';

const localize = nls.loadMessageBundle();

@injectable()
export class NodeDebugConfigurationProvider extends BaseConfigurationProvider<
export class NodeInitialDebugConfigurationProvider extends BaseConfigurationProvider<
AnyNodeConfiguration
> {
protected provide(folder?: vscode.WorkspaceFolder) {
Expand All @@ -25,3 +36,68 @@ export class NodeDebugConfigurationProvider extends BaseConfigurationProvider<
return vscode.DebugConfigurationProviderTrigger.Initial;
}
}

type DynamicConfig = AnyResolvingConfiguration[] | undefined;

@injectable()
export class NodeDynamicDebugConfigurationProvider extends BaseConfigurationProvider<
AnyNodeConfiguration | AnyTerminalConfiguration
> {
protected async provide(folder?: vscode.WorkspaceFolder) {
const candidates = await Promise.all([
this.getFromNpmScripts(folder),
this.getFromActiveFile(),
]);

return flatten(candidates.filter((c): c is ResolvingNodeLaunchConfiguration[] => !!c));
}

protected getType() {
return DebugType.Node as const;
}

protected getTriggerKind() {
return vscode.DebugConfigurationProviderTrigger.Dynamic;
}

/**
* Adds suggestions discovered from npm scripts.
*/
protected async getFromNpmScripts(folder?: vscode.WorkspaceFolder): Promise<DynamicConfig> {
if (!folder) {
return;
}

const scripts = await findScripts(folder, true);
return scripts?.map(script => ({
type: DebugType.Terminal,
name: localize('node.launch.script', 'Run Script: {0}', script.name),
request: 'launch',
command: script.command,
cwd: script.directory.uri.fsPath,
}));
}

/**
* Adds a suggestion to run the active file, if it's debuggable.
*/
protected getFromActiveFile(): DynamicConfig {
const editor = vscode.window.activeTextEditor;
if (
!editor ||
!breakpointLanguages.includes(editor.document.languageId) ||
editor.document.uri.scheme !== 'file'
) {
return;
}

return [
{
type: DebugType.Node,
name: localize('node.launch.currentFile', 'Run Current File'),
request: 'launch',
program: editor.document.uri.fsPath,
},
];
}
}
8 changes: 1 addition & 7 deletions src/ui/configuration/nodeDebugConfigurationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,10 @@ import { ExtensionContext } from '../../ioc-extras';
import { nearestDirectoryContaining } from '../../common/urlUtils';
import { isSubdirectoryOf, forceForwardSlashes } from '../../common/pathUtils';
import { resolveProcessId } from '../processPicker';

// eslint-disable-next-line
const config = require('../../../package.json');
import { breakpointLanguages } from '.';

const localize = nls.loadMessageBundle();

const breakpointLanguages: ReadonlyArray<string> = config.contributes.breakpoints.map(
(b: { language: string }) => b.language,
);

type ResolvingNodeConfiguration =
| ResolvingNodeAttachConfiguration
| ResolvingNodeLaunchConfiguration;
Expand Down
33 changes: 20 additions & 13 deletions src/ui/debugNpmScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,22 @@ const updateEditCandidate = (existing: IEditCandidate, updated: IEditCandidate)
/**
* Finds configured npm scripts in the workspace.
*/
async function findScripts(inFolder?: vscode.WorkspaceFolder): Promise<IScript[] | void> {
export async function findScripts(
inFolder?: vscode.WorkspaceFolder,
silent = false,
): Promise<IScript[] | undefined> {
const folders = inFolder ? [inFolder] : vscode.workspace.workspaceFolders;

// 1. If there are no open folders, show an error and abort.
if (!folders || folders.length === 0) {
vscode.window.showErrorMessage(
localize(
'debug.npm.noWorkspaceFolder',
'You need to open a workspace folder to debug npm scripts.',
),
);
if (!silent) {
vscode.window.showErrorMessage(
localize(
'debug.npm.noWorkspaceFolder',
'You need to open a workspace folder to debug npm scripts.',
),
);
}
return;
}

Expand Down Expand Up @@ -109,11 +114,13 @@ async function findScripts(inFolder?: vscode.WorkspaceFolder): Promise<IScript[]
try {
parsed = JSON.parse(await readfile(packageJson));
} catch (e) {
promptToOpen(
'showWarningMessage',
localize('debug.npm.parseError', 'Could not read {0}: {1}', packageJson, e.message),
packageJson,
);
if (!silent) {
promptToOpen(
'showWarningMessage',
localize('debug.npm.parseError', 'Could not read {0}: {1}', packageJson, e.message),
packageJson,
);
}
// set the candidate to 'undefined', since we already displayed an error
// and if there are no other candidates then that alone is fine.
editCandidate = updateEditCandidate(editCandidate, { path: undefined, score: 3 });
Expand All @@ -137,7 +144,7 @@ async function findScripts(inFolder?: vscode.WorkspaceFolder): Promise<IScript[]
}

if (scripts.length === 0) {
if (editCandidate.path) {
if (editCandidate.path && !silent) {
promptToOpen(
'showErrorMessage',
localize('debug.npm.noScripts', 'No npm scripts found in your package.json'),
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"noUnusedLocals": true,
"experimentalDecorators": true,
"alwaysStrict": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node", "chai-subset", "chai-as-promised", "mocha"]
Expand Down

0 comments on commit c41ec8c

Please sign in to comment.