mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 11:08:51 +01:00
🎁 Add killOnServerStop to debug configuration (#163779)
* 🎁 Add `killOnServerStop` to schema Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> * 📜 Add description for `killOnServerStop` Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> * 🔨 Stop created debug session on server stop Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> * 🔨 Push kill listeners into another disposable container Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> * 🐛 Prevent leak when new debug session fails to start Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> * 🔨 Use more verbose name for debug session tracker ID Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as util from 'util';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
const PATTERN = 'listening on.* (https?://\\S+|[0-9]+)'; // matches "listening on port 3000" or "Now listening on: https://localhost:5001"
|
||||
const URI_PORT_FORMAT = 'http://localhost:%s';
|
||||
@@ -17,6 +18,7 @@ interface ServerReadyAction {
|
||||
uriFormat?: string;
|
||||
webRoot?: string;
|
||||
name?: string;
|
||||
killOnServerStop?: boolean;
|
||||
}
|
||||
|
||||
class Trigger {
|
||||
@@ -40,6 +42,7 @@ class ServerReadyDetector extends vscode.Disposable {
|
||||
private shellPid?: number;
|
||||
private regexp: RegExp;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
private lateDisposables = new Set<vscode.Disposable>([]);
|
||||
|
||||
static start(session: vscode.DebugSession): ServerReadyDetector | undefined {
|
||||
if (session.configuration.serverReadyAction) {
|
||||
@@ -109,6 +112,11 @@ class ServerReadyDetector extends vscode.Disposable {
|
||||
this.disposables = [];
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
this.lateDisposables.forEach(d => d.dispose());
|
||||
return super.dispose();
|
||||
}
|
||||
|
||||
detectPattern(s: string): boolean {
|
||||
if (!this.trigger.hasFired) {
|
||||
const matches = this.regexp.exec(s);
|
||||
@@ -153,25 +161,25 @@ class ServerReadyDetector extends vscode.Disposable {
|
||||
this.openExternalWithUri(session, uri);
|
||||
}
|
||||
|
||||
private openExternalWithUri(session: vscode.DebugSession, uri: string) {
|
||||
private async openExternalWithUri(session: vscode.DebugSession, uri: string) {
|
||||
|
||||
const args: ServerReadyAction = session.configuration.serverReadyAction;
|
||||
switch (args.action || 'openExternally') {
|
||||
|
||||
case 'openExternally':
|
||||
vscode.env.openExternal(vscode.Uri.parse(uri));
|
||||
await vscode.env.openExternal(vscode.Uri.parse(uri));
|
||||
break;
|
||||
|
||||
case 'debugWithChrome':
|
||||
this.debugWithBrowser('pwa-chrome', session, uri);
|
||||
await this.debugWithBrowser('pwa-chrome', session, uri);
|
||||
break;
|
||||
|
||||
case 'debugWithEdge':
|
||||
this.debugWithBrowser('pwa-msedge', session, uri);
|
||||
await this.debugWithBrowser('pwa-msedge', session, uri);
|
||||
break;
|
||||
|
||||
case 'startDebugging':
|
||||
vscode.debug.startDebugging(session.workspaceFolder, args.name || 'unspecified');
|
||||
await this.startNamedDebugSession(session, args.name || 'unspecified');
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -180,13 +188,104 @@ class ServerReadyDetector extends vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) {
|
||||
private async debugWithBrowser(type: string, session: vscode.DebugSession, uri: string) {
|
||||
const args = session.configuration.serverReadyAction as ServerReadyAction;
|
||||
if (!args.killOnServerStop) {
|
||||
await this.startBrowserDebugSession(type, session, uri);
|
||||
return;
|
||||
}
|
||||
|
||||
const trackerId = randomUUID();
|
||||
const cts = new vscode.CancellationTokenSource();
|
||||
const newSessionPromise = this.catchStartedDebugSession(session => session.configuration._debugServerReadySessionId === trackerId, cts.token);
|
||||
|
||||
if (!await this.startBrowserDebugSession(type, session, uri, trackerId)) {
|
||||
cts.cancel();
|
||||
cts.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const createdSession = await newSessionPromise;
|
||||
cts.dispose();
|
||||
|
||||
if (!createdSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stopListener = vscode.debug.onDidTerminateDebugSession(async (terminated) => {
|
||||
if (terminated === session) {
|
||||
stopListener.dispose();
|
||||
this.lateDisposables.delete(stopListener);
|
||||
await vscode.debug.stopDebugging(createdSession);
|
||||
}
|
||||
});
|
||||
this.lateDisposables.add(stopListener);
|
||||
}
|
||||
|
||||
private startBrowserDebugSession(type: string, session: vscode.DebugSession, uri: string, trackerId?: string) {
|
||||
return vscode.debug.startDebugging(session.workspaceFolder, {
|
||||
type,
|
||||
name: 'Browser Debug',
|
||||
request: 'launch',
|
||||
url: uri,
|
||||
webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT
|
||||
webRoot: session.configuration.serverReadyAction.webRoot || WEB_ROOT,
|
||||
_debugServerReadySessionId: trackerId,
|
||||
});
|
||||
}
|
||||
|
||||
private async startNamedDebugSession(session: vscode.DebugSession, name: string) {
|
||||
const args = session.configuration.serverReadyAction as ServerReadyAction;
|
||||
if (!args.killOnServerStop) {
|
||||
await vscode.debug.startDebugging(session.workspaceFolder, name);
|
||||
return;
|
||||
}
|
||||
|
||||
const cts = new vscode.CancellationTokenSource();
|
||||
const newSessionPromise = this.catchStartedDebugSession(x => x.name === name, cts.token);
|
||||
|
||||
if (!await vscode.debug.startDebugging(session.workspaceFolder, name)) {
|
||||
cts.cancel();
|
||||
cts.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const createdSession = await newSessionPromise;
|
||||
cts.dispose();
|
||||
|
||||
if (!createdSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stopListener = vscode.debug.onDidTerminateDebugSession(async (terminated) => {
|
||||
if (terminated === session) {
|
||||
stopListener.dispose();
|
||||
this.lateDisposables.delete(stopListener);
|
||||
await vscode.debug.stopDebugging(createdSession);
|
||||
}
|
||||
});
|
||||
this.lateDisposables.add(stopListener);
|
||||
}
|
||||
|
||||
private catchStartedDebugSession(predicate: (session: vscode.DebugSession) => boolean, cancellationToken: vscode.CancellationToken): Promise<vscode.DebugSession | undefined> {
|
||||
return new Promise<vscode.DebugSession | undefined>(_resolve => {
|
||||
const done = (value?: vscode.DebugSession) => {
|
||||
listener.dispose();
|
||||
cancellationListener.dispose();
|
||||
this.lateDisposables.delete(listener);
|
||||
this.lateDisposables.delete(cancellationListener);
|
||||
_resolve(value);
|
||||
};
|
||||
|
||||
const cancellationListener = cancellationToken.onCancellationRequested(done);
|
||||
const listener = vscode.debug.onDidStartDebugSession(session => {
|
||||
if (predicate(session)) {
|
||||
done(session);
|
||||
}
|
||||
});
|
||||
|
||||
// In case the debug session of interest was never caught anyhow.
|
||||
this.lateDisposables.add(listener);
|
||||
this.lateDisposables.add(cancellationListener);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user