chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
legacy-peer-deps="true"
|
||||
timeout=180000
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Server Ready Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
src/**
|
||||
tsconfig.json
|
||||
out/**
|
||||
extension.webpack.config.js
|
||||
package-lock.json
|
||||
.vscode
|
||||
@@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/extension.ts',
|
||||
},
|
||||
resolve: {
|
||||
mainFields: ['module', 'main']
|
||||
}
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "debug-server-ready",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "debug-server-ready",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "20.x"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.32.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz",
|
||||
"integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"name": "debug-server-ready",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.0",
|
||||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.32.0"
|
||||
},
|
||||
"icon": "media/icon.png",
|
||||
"activationEvents": [
|
||||
"onDebugResolve"
|
||||
],
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": false,
|
||||
"untrustedWorkspaces": {
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"enabledApiProposals": [
|
||||
"terminalDataWriteEvent"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:debug-server-ready",
|
||||
"watch": "gulp watch-extension:debug-server-ready"
|
||||
},
|
||||
"contributes": {
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "*",
|
||||
"configurationAttributes": {
|
||||
"launch": {
|
||||
"properties": {
|
||||
"serverReadyAction": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"markdownDescription": "%debug.server.ready.serverReadyAction.description%",
|
||||
"default": {
|
||||
"action": "openExternally",
|
||||
"killOnServerStop": false
|
||||
},
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"openExternally"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%debug.server.ready.action.openExternally.description%"
|
||||
],
|
||||
"markdownDescription": "%debug.server.ready.action.description%",
|
||||
"default": "openExternally"
|
||||
},
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.pattern.description%",
|
||||
"default": "listening on port ([0-9]+)"
|
||||
},
|
||||
"uriFormat": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.uriFormat.description%",
|
||||
"default": "http://localhost:%s"
|
||||
},
|
||||
"killOnServerStop": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%debug.server.ready.killOnServerStop.description%",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"markdownDescription": "%debug.server.ready.serverReadyAction.description%",
|
||||
"default": {
|
||||
"action": "debugWithEdge",
|
||||
"pattern": "listening on port ([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"killOnServerStop": false
|
||||
},
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"debugWithChrome",
|
||||
"debugWithEdge"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%debug.server.ready.action.debugWithChrome.description%"
|
||||
],
|
||||
"markdownDescription": "%debug.server.ready.action.description%",
|
||||
"default": "debugWithEdge"
|
||||
},
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.pattern.description%",
|
||||
"default": "listening on port ([0-9]+)"
|
||||
},
|
||||
"uriFormat": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.uriFormat.description%",
|
||||
"default": "http://localhost:%s"
|
||||
},
|
||||
"webRoot": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.webRoot.description%",
|
||||
"default": "${workspaceFolder}"
|
||||
},
|
||||
"killOnServerStop": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%debug.server.ready.killOnServerStop.description%",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"markdownDescription": "%debug.server.ready.serverReadyAction.description%",
|
||||
"default": {
|
||||
"action": "startDebugging",
|
||||
"name": "<launch browser config name>",
|
||||
"killOnServerStop": false
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"startDebugging"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%debug.server.ready.action.startDebugging.description%"
|
||||
],
|
||||
"markdownDescription": "%debug.server.ready.action.description%",
|
||||
"default": "startDebugging"
|
||||
},
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.pattern.description%",
|
||||
"default": "listening on port ([0-9]+)"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.debugConfigName.description%",
|
||||
"default": "Launch Browser"
|
||||
},
|
||||
"killOnServerStop": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%debug.server.ready.killOnServerStop.description%",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"markdownDescription": "%debug.server.ready.serverReadyAction.description%",
|
||||
"default": {
|
||||
"action": "startDebugging",
|
||||
"config": {
|
||||
"type": "node",
|
||||
"request": "launch"
|
||||
},
|
||||
"killOnServerStop": false
|
||||
},
|
||||
"required": [
|
||||
"config"
|
||||
],
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"startDebugging"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%debug.server.ready.action.startDebugging.description%"
|
||||
],
|
||||
"markdownDescription": "%debug.server.ready.action.description%",
|
||||
"default": "startDebugging"
|
||||
},
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%debug.server.ready.pattern.description%",
|
||||
"default": "listening on port ([0-9]+)"
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"markdownDescription": "%debug.server.ready.debugConfig.description%",
|
||||
"default": {}
|
||||
},
|
||||
"killOnServerStop": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%debug.server.ready.killOnServerStop.description%",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.x"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoft/vscode.git"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"displayName": "Server Ready Action",
|
||||
"description": "Open URI in browser if server under debugging is ready.",
|
||||
|
||||
"debug.server.ready.serverReadyAction.description": "Act upon a URI when a server program under debugging is ready (indicated by sending output of the form 'listening on port 3000' or 'Now listening on: https://localhost:5001' to the debug console.)",
|
||||
"debug.server.ready.action.description": "What to do with the URI when the server is ready.",
|
||||
"debug.server.ready.action.openExternally.description": "Open URI externally with the default application.",
|
||||
"debug.server.ready.action.debugWithChrome.description": "Start debugging with the 'Debugger for Chrome'.",
|
||||
"debug.server.ready.action.startDebugging.description": "Run another launch configuration.",
|
||||
"debug.server.ready.pattern.description": "Server is ready if this pattern appears on the debug console. The first capture group must include a URI or a port number.",
|
||||
"debug.server.ready.debugConfig.description": "The debug configuration to run.",
|
||||
"debug.server.ready.uriFormat.description": "A format string used when constructing the URI from a port number. The first '%s' is substituted with the port number.",
|
||||
"debug.server.ready.webRoot.description": "Value passed to the debug configuration for the 'Debugger for Chrome'.",
|
||||
"debug.server.ready.killOnServerStop.description": "Stop the child session when the parent session stopped.",
|
||||
"debug.server.ready.debugConfigName.description": "Name of the launch configuration to run."
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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';
|
||||
const URI_FORMAT = '%s';
|
||||
const WEB_ROOT = '${workspaceFolder}';
|
||||
|
||||
interface ServerReadyAction {
|
||||
pattern: string;
|
||||
action?: 'openExternally' | 'debugWithChrome' | 'debugWithEdge' | 'startDebugging';
|
||||
uriFormat?: string;
|
||||
webRoot?: string;
|
||||
name?: string;
|
||||
config?: vscode.DebugConfiguration;
|
||||
killOnServerStop?: boolean;
|
||||
}
|
||||
|
||||
// From src/vs/base/common/strings.ts
|
||||
const CSI_SEQUENCE = /(?:\x1b\[|\x9b)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/;
|
||||
const OSC_SEQUENCE = /(?:\x1b\]|\x9d).*?(?:\x1b\\|\x07|\x9c)/;
|
||||
const ESC_SEQUENCE = /\x1b(?:[ #%\(\)\*\+\-\.\/]?[a-zA-Z0-9\|}~@])/;
|
||||
const CONTROL_SEQUENCES = new RegExp('(?:' + [
|
||||
CSI_SEQUENCE.source,
|
||||
OSC_SEQUENCE.source,
|
||||
ESC_SEQUENCE.source,
|
||||
].join('|') + ')', 'g');
|
||||
|
||||
/**
|
||||
* Froms vs/base/common/strings.ts in core
|
||||
* @see https://github.com/microsoft/vscode/blob/22a2a0e833175c32a2005b977d7fbd355582e416/src/vs/base/common/strings.ts#L736
|
||||
*/
|
||||
function removeAnsiEscapeCodes(str: string): string {
|
||||
if (str) {
|
||||
str = str.replace(CONTROL_SEQUENCES, '');
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
class Trigger {
|
||||
private _fired = false;
|
||||
|
||||
public get hasFired() {
|
||||
return this._fired;
|
||||
}
|
||||
|
||||
public fire() {
|
||||
this._fired = true;
|
||||
}
|
||||
}
|
||||
|
||||
class ServerReadyDetector extends vscode.Disposable {
|
||||
|
||||
private static detectors = new Map<vscode.DebugSession, ServerReadyDetector>();
|
||||
private static terminalDataListener: vscode.Disposable | undefined;
|
||||
|
||||
private readonly stoppedEmitter = new vscode.EventEmitter<void>();
|
||||
private readonly onDidSessionStop = this.stoppedEmitter.event;
|
||||
private readonly disposables = new Set<vscode.Disposable>([]);
|
||||
private trigger: Trigger;
|
||||
private shellPid?: number;
|
||||
private regexp: RegExp;
|
||||
|
||||
static start(session: vscode.DebugSession): ServerReadyDetector | undefined {
|
||||
if (session.configuration.serverReadyAction) {
|
||||
let detector = ServerReadyDetector.detectors.get(session);
|
||||
if (!detector) {
|
||||
detector = new ServerReadyDetector(session);
|
||||
ServerReadyDetector.detectors.set(session, detector);
|
||||
}
|
||||
return detector;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static stop(session: vscode.DebugSession): void {
|
||||
const detector = ServerReadyDetector.detectors.get(session);
|
||||
if (detector) {
|
||||
ServerReadyDetector.detectors.delete(session);
|
||||
detector.sessionStopped();
|
||||
detector.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
static rememberShellPid(session: vscode.DebugSession, pid: number) {
|
||||
const detector = ServerReadyDetector.detectors.get(session);
|
||||
if (detector) {
|
||||
detector.shellPid = pid;
|
||||
}
|
||||
}
|
||||
|
||||
static async startListeningTerminalData() {
|
||||
if (!this.terminalDataListener) {
|
||||
this.terminalDataListener = vscode.window.onDidWriteTerminalData(async e => {
|
||||
|
||||
// first find the detector with a matching pid
|
||||
const pid = await e.terminal.processId;
|
||||
const str = removeAnsiEscapeCodes(e.data);
|
||||
for (const [, detector] of this.detectors) {
|
||||
if (detector.shellPid === pid) {
|
||||
detector.detectPattern(str);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if none found, try all detectors until one matches
|
||||
for (const [, detector] of this.detectors) {
|
||||
if (detector.detectPattern(str)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(private session: vscode.DebugSession) {
|
||||
super(() => this.internalDispose());
|
||||
|
||||
// Re-used the triggered of the parent session, if one exists
|
||||
if (session.parentSession) {
|
||||
this.trigger = ServerReadyDetector.start(session.parentSession)?.trigger ?? new Trigger();
|
||||
} else {
|
||||
this.trigger = new Trigger();
|
||||
}
|
||||
|
||||
this.regexp = new RegExp(session.configuration.serverReadyAction.pattern || PATTERN, 'i');
|
||||
}
|
||||
|
||||
private internalDispose() {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
this.disposables.clear();
|
||||
}
|
||||
|
||||
public sessionStopped() {
|
||||
this.stoppedEmitter.fire();
|
||||
}
|
||||
|
||||
detectPattern(s: string): boolean {
|
||||
if (!this.trigger.hasFired) {
|
||||
const matches = this.regexp.exec(s);
|
||||
if (matches && matches.length >= 1) {
|
||||
this.openExternalWithString(this.session, matches.length > 1 ? matches[1] : '');
|
||||
this.trigger.fire();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private openExternalWithString(session: vscode.DebugSession, captureString: string) {
|
||||
const args: ServerReadyAction = session.configuration.serverReadyAction;
|
||||
|
||||
let uri;
|
||||
if (captureString === '') {
|
||||
// nothing captured by reg exp -> use the uriFormat as the target uri without substitution
|
||||
// verify that format does not contain '%s'
|
||||
const format = args.uriFormat || '';
|
||||
if (format.indexOf('%s') >= 0) {
|
||||
const errMsg = vscode.l10n.t("Format uri ('{0}') uses a substitution placeholder but pattern did not capture anything.", format);
|
||||
vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined);
|
||||
return;
|
||||
}
|
||||
uri = format;
|
||||
} else {
|
||||
// if no uriFormat is specified guess the appropriate format based on the captureString
|
||||
const format = args.uriFormat || (/^[0-9]+$/.test(captureString) ? URI_PORT_FORMAT : URI_FORMAT);
|
||||
// verify that format only contains a single '%s'
|
||||
const s = format.split('%s');
|
||||
if (s.length !== 2) {
|
||||
const errMsg = vscode.l10n.t("Format uri ('{0}') must contain exactly one substitution placeholder.", format);
|
||||
vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined);
|
||||
return;
|
||||
}
|
||||
uri = util.format(format, captureString);
|
||||
}
|
||||
|
||||
this.openExternalWithUri(session, uri);
|
||||
}
|
||||
|
||||
private async openExternalWithUri(session: vscode.DebugSession, uri: string) {
|
||||
|
||||
const args: ServerReadyAction = session.configuration.serverReadyAction;
|
||||
switch (args.action || 'openExternally') {
|
||||
|
||||
case 'openExternally':
|
||||
await vscode.env.openExternal(vscode.Uri.parse(uri));
|
||||
break;
|
||||
|
||||
case 'debugWithChrome':
|
||||
await this.debugWithBrowser('pwa-chrome', session, uri);
|
||||
break;
|
||||
|
||||
case 'debugWithEdge':
|
||||
await this.debugWithBrowser('pwa-msedge', session, uri);
|
||||
break;
|
||||
|
||||
case 'startDebugging':
|
||||
if (args.config) {
|
||||
await this.startDebugSession(session, args.config.name, args.config);
|
||||
} else {
|
||||
await this.startDebugSession(session, args.name || 'unspecified');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// not supported
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 = this.onDidSessionStop(async () => {
|
||||
stopListener.dispose();
|
||||
this.disposables.delete(stopListener);
|
||||
await vscode.debug.stopDebugging(createdSession);
|
||||
});
|
||||
this.disposables.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,
|
||||
_debugServerReadySessionId: trackerId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a debug session given a debug configuration name (saved in launch.json) or a debug configuration object.
|
||||
*
|
||||
* @param session The parent debugSession
|
||||
* @param name The name of the configuration to launch. If config it set, it assumes it is the same as config.name.
|
||||
* @param config [Optional] Instead of starting a debug session by debug configuration name, use a debug configuration object instead.
|
||||
*/
|
||||
private async startDebugSession(session: vscode.DebugSession, name: string, config?: vscode.DebugConfiguration) {
|
||||
const args = session.configuration.serverReadyAction as ServerReadyAction;
|
||||
if (!args.killOnServerStop) {
|
||||
await vscode.debug.startDebugging(session.workspaceFolder, config ?? name);
|
||||
return;
|
||||
}
|
||||
|
||||
const cts = new vscode.CancellationTokenSource();
|
||||
const newSessionPromise = this.catchStartedDebugSession(x => x.name === name, cts.token);
|
||||
|
||||
if (!await vscode.debug.startDebugging(session.workspaceFolder, config ?? name)) {
|
||||
cts.cancel();
|
||||
cts.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const createdSession = await newSessionPromise;
|
||||
cts.dispose();
|
||||
|
||||
if (!createdSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stopListener = this.onDidSessionStop(async () => {
|
||||
stopListener.dispose();
|
||||
this.disposables.delete(stopListener);
|
||||
await vscode.debug.stopDebugging(createdSession);
|
||||
});
|
||||
this.disposables.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.disposables.delete(listener);
|
||||
this.disposables.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.disposables.add(listener);
|
||||
this.disposables.add(cancellationListener);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(vscode.debug.onDidStartDebugSession(session => {
|
||||
if (session.configuration.serverReadyAction) {
|
||||
const detector = ServerReadyDetector.start(session);
|
||||
if (detector) {
|
||||
ServerReadyDetector.startListeningTerminalData();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(session => {
|
||||
ServerReadyDetector.stop(session);
|
||||
}));
|
||||
|
||||
const trackers = new Set<string>();
|
||||
|
||||
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('*', {
|
||||
resolveDebugConfigurationWithSubstitutedVariables(_folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration) {
|
||||
if (debugConfiguration.type && debugConfiguration.serverReadyAction) {
|
||||
if (!trackers.has(debugConfiguration.type)) {
|
||||
trackers.add(debugConfiguration.type);
|
||||
startTrackerForType(context, debugConfiguration.type);
|
||||
}
|
||||
}
|
||||
return debugConfiguration;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function startTrackerForType(context: vscode.ExtensionContext, type: string) {
|
||||
|
||||
// scan debug console output for a PORT message
|
||||
context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory(type, {
|
||||
createDebugAdapterTracker(session: vscode.DebugSession) {
|
||||
const detector = ServerReadyDetector.start(session);
|
||||
if (detector) {
|
||||
let runInTerminalRequestSeq: number | undefined;
|
||||
return {
|
||||
onDidSendMessage: m => {
|
||||
if (m.type === 'event' && m.event === 'output' && m.body) {
|
||||
switch (m.body.category) {
|
||||
case 'console':
|
||||
case 'stderr':
|
||||
case 'stdout':
|
||||
if (m.body.output) {
|
||||
detector.detectPattern(m.body.output);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m.type === 'request' && m.command === 'runInTerminal' && m.arguments) {
|
||||
if (m.arguments.kind === 'integrated') {
|
||||
runInTerminalRequestSeq = m.seq; // remember this to find matching response
|
||||
}
|
||||
}
|
||||
},
|
||||
onWillReceiveMessage: m => {
|
||||
if (runInTerminalRequestSeq && m.type === 'response' && m.command === 'runInTerminal' && m.body && runInTerminalRequestSeq === m.request_seq) {
|
||||
runInTerminalRequestSeq = undefined;
|
||||
ServerReadyDetector.rememberShellPid(session, m.body.shellProcessId);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"downlevelIteration": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts",
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user