Skip to content

Commit e249fb1

Browse files
committed
feat: allow pointing to a node source file to execute
Fixes #291. Involves some churn since I wanted to avoid having to run the breakpoint predictor twice: - The path resolver is now created by a separate, session-level factory, rather than each target. This is needed since the path resolver is used in the predictor, which is used before we launch Node or have any targets. - The initialize params/client capabilities are now in IOC. Cleans up a decent amount of code. - Then with these changes, using the breakpoint predictor in the Node launcher to look up the compiled path is quite simple :P
1 parent 7f8f63f commit e249fb1

15 files changed

+253
-115
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// A launch configuration that compiles the extension and then opens it inside a new window
2+
{
3+
"version": "0.2.0",
4+
"configurations": [
5+
{
6+
"name": "Launch Extension",
7+
"type": "extensionHost",
8+
"request": "launch",
9+
"runtimeExecutable": "${execPath}",
10+
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
11+
"stopOnEntry": false,
12+
"sourceMaps": true,
13+
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
14+
"preLaunchTask": "npm: watch"
15+
}
16+
]
17+
}

demos/extension/.vscode/tasks.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"type": "npm",
8+
"script": "watch",
9+
"problemMatcher": ["$tsc-watch"],
10+
"isBackground": true
11+
}
12+
]
13+
}

src/adapter/breakpointPredictor.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export const IBreakpointsPredictor = Symbol('IBreakpointsPredictor');
4141
* by looking at source maps on disk.
4242
*/
4343
export interface IBreakpointsPredictor {
44+
/**
45+
* Gets prediction data for the given source file path, if it exists.
46+
*/
47+
getPredictionForSource(sourceFile: string): Promise<ReadonlySet<DiscoveredMetadata> | undefined>;
48+
4449
/**
4550
* Returns a promise that resolves once maps in the root are predicted.
4651
*/
@@ -240,6 +245,17 @@ export class BreakpointsPredictor implements IBreakpointsPredictor {
240245
}
241246
}
242247

248+
/**
249+
* @inheritdoc
250+
*/
251+
public async getPredictionForSource(sourcePath: string) {
252+
if (!this.sourcePathToCompiled) {
253+
this.sourcePathToCompiled = this.createInitialMapping();
254+
}
255+
256+
return (await this.sourcePathToCompiled).get(sourcePath);
257+
}
258+
243259
/**
244260
* Returns predicted breakpoint locations for the provided source.
245261
*/

src/binder.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import * as os from 'os';
3333
import { delay } from './common/promiseUtil';
3434
import { Container } from 'inversify';
3535
import { createTargetContainer, provideLaunchParams } from './ioc';
36-
import { disposeContainer } from './ioc-extras';
36+
import { disposeContainer, IInitializeParams } from './ioc-extras';
3737

3838
const localize = nls.loadMessageBundle();
3939

@@ -51,16 +51,15 @@ export class Binder implements IDisposable {
5151
private _delegate: IBinderDelegate;
5252
private _disposables: IDisposable[];
5353
private _threads = new Map<ITarget, { thread: Thread; debugAdapter: DebugAdapter }>();
54-
private _launchers = new Set<ILauncher>();
5554
private _terminationCount = 0;
5655
private _onTargetListChangedEmitter = new EventEmitter<void>();
5756
readonly onTargetListChanged = this._onTargetListChangedEmitter.event;
5857
private _dap: Promise<Dap.Api>;
5958
private _targetOrigin: ITargetOrigin;
6059
private _launchParams?: AnyLaunchConfiguration;
61-
private _clientCapabilities: Dap.InitializeParams | undefined;
6260
private _asyncStackPolicy?: IAsyncStackPolicy;
6361
private _serviceTree = new WeakMap<ITarget, Container>();
62+
private _launchers?: ReadonlySet<ILauncher>;
6463

6564
constructor(
6665
delegate: IBinderDelegate,
@@ -69,7 +68,6 @@ export class Binder implements IDisposable {
6968
private readonly _rootServices: Container,
7069
targetOrigin: ITargetOrigin,
7170
) {
72-
this._launchers = new Set(_rootServices.getAll(ILauncher));
7371
this._delegate = delegate;
7472
this._dap = connection.dap();
7573
this._targetOrigin = targetOrigin;
@@ -79,23 +77,9 @@ export class Binder implements IDisposable {
7977
installUnhandledErrorReporter(_rootServices.get(ILogger), telemetryReporter),
8078
];
8179

82-
for (const launcher of this._launchers) {
83-
this._launchers.add(launcher);
84-
launcher.onTargetListChanged(
85-
() => {
86-
const targets = this.targetList();
87-
this._attachToNewTargets(targets, launcher);
88-
this._detachOrphanThreads(targets);
89-
this._onTargetListChangedEmitter.fire();
90-
},
91-
undefined,
92-
this._disposables,
93-
);
94-
}
95-
9680
this._dap.then(dap => {
9781
dap.on('initialize', async clientCapabilities => {
98-
this._clientCapabilities = clientCapabilities;
82+
this._rootServices.bind(IInitializeParams).toConstantValue(clientCapabilities);
9983
const capabilities = DebugAdapter.capabilities();
10084
if (clientCapabilities.clientID === 'vscode') {
10185
filterErrorsReportedToTelemetry();
@@ -141,8 +125,29 @@ export class Binder implements IDisposable {
141125
});
142126
}
143127

128+
private getLaunchers() {
129+
if (!this._launchers) {
130+
this._launchers = new Set(this._rootServices.getAll(ILauncher));
131+
132+
for (const launcher of this._launchers) {
133+
launcher.onTargetListChanged(
134+
() => {
135+
const targets = this.targetList();
136+
this._attachToNewTargets(targets, launcher);
137+
this._detachOrphanThreads(targets);
138+
this._onTargetListChangedEmitter.fire();
139+
},
140+
undefined,
141+
this._disposables,
142+
);
143+
}
144+
}
145+
146+
return this._launchers;
147+
}
148+
144149
private async _disconnect() {
145-
await Promise.all([...this._launchers].map(l => l.disconnect()));
150+
await Promise.all([...this.getLaunchers()].map(l => l.disconnect()));
146151

147152
const didTerminate = () => !this.targetList.length && this._terminationCount === 0;
148153
if (didTerminate()) {
@@ -174,7 +179,7 @@ export class Binder implements IDisposable {
174179
if (params.rootPath) params.rootPath = urlUtils.platformPathToPreferredCase(params.rootPath);
175180
this._launchParams = params;
176181
let results = await Promise.all(
177-
[...this._launchers].map(l => this._launch(l, params, cts.token)),
182+
[...this.getLaunchers()].map(l => this._launch(l, params, cts.token)),
178183
);
179184
results = results.filter(result => !!result);
180185
if (results.length) return errors.createUserError(results.join('\n'));
@@ -217,7 +222,7 @@ export class Binder implements IDisposable {
217222
}
218223

219224
async _restart() {
220-
await Promise.all([...this._launchers].map(l => l.restart()));
225+
await Promise.all([...this.getLaunchers()].map(l => l.restart()));
221226
}
222227

223228
async _launch(
@@ -263,16 +268,12 @@ export class Binder implements IDisposable {
263268

264269
let result: ILaunchResult;
265270
try {
266-
result = await launcher.launch(
267-
params,
268-
{
269-
telemetryReporter: this.telemetryReporter,
270-
cancellationToken,
271-
targetOrigin: this._targetOrigin,
272-
dap: await this._dap,
273-
},
274-
this._clientCapabilities!,
275-
);
271+
result = await launcher.launch(params, {
272+
telemetryReporter: this.telemetryReporter,
273+
cancellationToken,
274+
targetOrigin: this._targetOrigin,
275+
dap: await this._dap,
276+
});
276277
} catch (e) {
277278
if (e instanceof TaskCancelledError) {
278279
result = {
@@ -310,15 +311,16 @@ export class Binder implements IDisposable {
310311
dispose() {
311312
for (const disposable of this._disposables) disposable.dispose();
312313
this._disposables = [];
313-
for (const launcher of this._launchers) launcher.dispose();
314-
this._launchers.clear();
315314
disposeContainer(this._rootServices);
316315
this._detachOrphanThreads([]);
317316
}
318317

319318
targetList(): ITarget[] {
320319
const result: ITarget[] = [];
321-
for (const delegate of this._launchers) result.push(...delegate.targetList());
320+
for (const delegate of this.getLaunchers()) {
321+
result.push(...delegate.targetList());
322+
}
323+
322324
return result;
323325
}
324326

src/ioc-extras.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import { promises as fsPromises } from 'fs';
1111
*/
1212
export const StoragePath = Symbol('StoragePath');
1313

14+
/**
15+
* Key for the Dap.InitializeParams.
16+
*/
17+
export const IInitializeParams = Symbol('IInitializeParams');
18+
1419
/**
1520
* Key for whether vs code services are available here.
1621
*/

src/ioc.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { IDebugConfigurationProvider } from './ui/configuration/configurationPro
5656
import execa from 'execa';
5757
import { promises as fsPromises } from 'fs';
5858
import { RemoteBrowserLauncher } from './targets/browser/remoteBrowserLauncher';
59+
import { SourcePathResolverFactory } from './targets/sourcePathResolverFactory';
5960

6061
/**
6162
* Contains IOC container factories for the extension. We use Inverisfy, which
@@ -95,14 +96,6 @@ export const createTargetContainer = (
9596
.to(ScriptSkipper)
9697
.inSingletonScope();
9798

98-
// first/parent target
99-
if (!parent.isBound(ITarget)) {
100-
container
101-
.bind(IBreakpointsPredictor)
102-
.to(BreakpointsPredictor)
103-
.inSingletonScope();
104-
}
105-
10699
return container;
107100
};
108101

@@ -132,6 +125,11 @@ export const createTopLevelSessionContainer = (parent: Container) => {
132125
.inSingletonScope()
133126
.onActivation(trackDispose);
134127

128+
container
129+
.bind(IBreakpointsPredictor)
130+
.to(BreakpointsPredictor)
131+
.inSingletonScope();
132+
135133
container
136134
.bind(ISourceMapRepository)
137135
.toDynamicValue(ctx =>
@@ -273,5 +271,16 @@ export const createGlobalContainer = (options: {
273271
return container;
274272
};
275273

276-
export const provideLaunchParams = (container: Container, params: AnyLaunchConfiguration) =>
274+
export const provideLaunchParams = (container: Container, params: AnyLaunchConfiguration) => {
277275
container.bind(AnyLaunchConfiguration).toConstantValue(params);
276+
277+
container
278+
.bind(SourcePathResolverFactory)
279+
.toSelf()
280+
.inSingletonScope();
281+
282+
container
283+
.bind(ISourcePathResolver)
284+
.toDynamicValue(ctx => ctx.container.get(SourcePathResolverFactory).create())
285+
.inSingletonScope();
286+
};

src/targets/browser/browserAttacher.ts

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,17 @@ import * as launcher from './launcher';
88
import * as nls from 'vscode-nls';
99
import { BrowserTargetManager } from './browserTargets';
1010
import { ITarget, ILauncher, ILaunchResult, ILaunchContext, IStopMetadata } from '../targets';
11-
import { BrowserSourcePathResolver } from './browserPathResolver';
12-
import { baseURL } from './browserLaunchParams';
1311
import { AnyLaunchConfiguration, IChromeAttachConfiguration } from '../../configuration';
1412
import { DebugType } from '../../common/contributionUtils';
1513
import { TelemetryReporter } from '../../telemetry/telemetryReporter';
1614
import { createTargetFilterForConfig } from '../../common/urlUtils';
1715
import { delay } from '../../common/promiseUtil';
1816
import { CancellationToken } from 'vscode';
1917
import { NeverCancelled } from '../../common/cancellation';
20-
import { Dap } from '../../dap/api';
2118
import { ITargetOrigin } from '../targetOrigin';
2219
import { ILogger } from '../../common/logging';
2320
import { injectable, inject } from 'inversify';
21+
import { ISourcePathResolver } from '../../common/sourcePathResolver';
2422

2523
const localize = nls.loadMessageBundle();
2624

@@ -37,7 +35,10 @@ export class BrowserAttacher implements ILauncher {
3735
private _onTargetListChangedEmitter = new EventEmitter<void>();
3836
readonly onTargetListChanged = this._onTargetListChangedEmitter.event;
3937

40-
constructor(@inject(ILogger) private readonly logger: ILogger) {}
38+
constructor(
39+
@inject(ILogger) private readonly logger: ILogger,
40+
@inject(ISourcePathResolver) private readonly pathResolver: ISourcePathResolver,
41+
) {}
4142

4243
dispose() {
4344
for (const disposable of this._disposables) disposable.dispose();
@@ -49,7 +50,6 @@ export class BrowserAttacher implements ILauncher {
4950
async launch(
5051
params: AnyLaunchConfiguration,
5152
{ targetOrigin, cancellationToken, telemetryReporter }: ILaunchContext,
52-
clientCapabilities: Dap.InitializeParams,
5353
): Promise<ILaunchResult> {
5454
if (params.type !== DebugType.Chrome || params.request !== 'attach') {
5555
return { blockSessionTermination: false };
@@ -58,27 +58,19 @@ export class BrowserAttacher implements ILauncher {
5858
this._launchParams = params;
5959
this._targetOrigin = targetOrigin;
6060

61-
const error = await this._attemptToAttach(
62-
telemetryReporter,
63-
clientCapabilities,
64-
cancellationToken,
65-
);
61+
const error = await this._attemptToAttach(telemetryReporter, cancellationToken);
6662
return error ? { error } : { blockSessionTermination: false };
6763
}
6864

69-
_scheduleAttach(
70-
rawTelemetryReporter: TelemetryReporter,
71-
clientCapabilities: Dap.InitializeParams,
72-
) {
65+
_scheduleAttach(rawTelemetryReporter: TelemetryReporter) {
7366
this._attemptTimer = setTimeout(() => {
7467
this._attemptTimer = undefined;
75-
this._attemptToAttach(rawTelemetryReporter, clientCapabilities, NeverCancelled);
68+
this._attemptToAttach(rawTelemetryReporter, NeverCancelled);
7669
}, 1000);
7770
}
7871

7972
async _attemptToAttach(
8073
rawTelemetryReporter: TelemetryReporter,
81-
clientCapabilities: Dap.InitializeParams,
8274
cancellationToken: CancellationToken,
8375
) {
8476
const params = this._launchParams!;
@@ -101,29 +93,17 @@ export class BrowserAttacher implements ILauncher {
10193
this._onTargetListChangedEmitter.fire();
10294
}
10395
if (this._launchParams === params) {
104-
this._scheduleAttach(rawTelemetryReporter, clientCapabilities);
96+
this._scheduleAttach(rawTelemetryReporter);
10597
}
10698
},
10799
undefined,
108100
this._disposables,
109101
);
110102

111-
const pathResolver = new BrowserSourcePathResolver(
112-
{
113-
resolveSourceMapLocations: params.resolveSourceMapLocations,
114-
baseUrl: baseURL(params),
115-
localRoot: null,
116-
remoteRoot: null,
117-
pathMapping: { '/': params.webRoot, ...params.pathMapping },
118-
sourceMapOverrides: params.sourceMapPathOverrides,
119-
clientID: clientCapabilities.clientID,
120-
},
121-
this.logger,
122-
);
123103
this._targetManager = await BrowserTargetManager.connect(
124104
connection,
125105
undefined,
126-
pathResolver,
106+
this.pathResolver,
127107
params,
128108
this.logger,
129109
rawTelemetryReporter,

0 commit comments

Comments
 (0)