-
Notifications
You must be signed in to change notification settings - Fork 269
/
terminalNodeLauncher.ts
173 lines (151 loc) · 5.49 KB
/
terminalNodeLauncher.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { randomBytes } from 'crypto';
import { inject, injectable } from 'inversify';
import { tmpdir } from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { IPortLeaseTracker } from '../../adapter/portLeaseTracker';
import { DebugType } from '../../common/contributionUtils';
import { EventEmitter } from '../../common/events';
import { ILogger } from '../../common/logging';
import { delay } from '../../common/promiseUtil';
import { AnyLaunchConfiguration, ITerminalLaunchConfiguration } from '../../configuration';
import { ErrorCodes } from '../../dap/errors';
import { ProtocolError } from '../../dap/protocolError';
import { FS, FsPromises } from '../../ioc-extras';
import { ISourcePathResolverFactory } from '../sourcePathResolverFactory';
import { IStopMetadata, ITarget } from '../targets';
import { hideDebugInfoFromConsole, INodeBinaryProvider, NodeBinary } from './nodeBinaryProvider';
import { IProcessTelemetry, IRunData, NodeLauncherBase } from './nodeLauncherBase';
import { IProgram } from './program';
class VSCodeTerminalProcess implements IProgram {
public readonly stopped: Promise<IStopMetadata>;
constructor(private readonly terminal: vscode.Terminal) {
this.stopped = new Promise(resolve => {
const disposable = vscode.window.onDidCloseTerminal(t => {
if (t === terminal) {
resolve({ code: 0, killed: true });
disposable.dispose();
}
});
});
}
public gotTelemetery() {
// no-op
}
public stop() {
// send ctrl+c to sigint any running processs (vscode/#108289)
this.terminal.sendText('\x03');
// and then destroy it on the next event loop tick
setTimeout(() => this.terminal.dispose(), 1);
return this.stopped;
}
}
export interface ITerminalLauncherLike extends NodeLauncherBase<ITerminalLaunchConfiguration> {
/**
* Gets telemetry of the last-started process.
*/
getProcessTelemetry(target: ITarget): Promise<IProcessTelemetry | undefined>;
}
/**
* A special launcher which only opens a vscode terminal. Used for the
* "debugger terminal" in the extension.
*/
@injectable()
export class TerminalNodeLauncher extends NodeLauncherBase<ITerminalLaunchConfiguration> {
private terminalCreatedEmitter = new EventEmitter<vscode.Terminal>();
protected callbackFile = path.join(
tmpdir(),
`node-debug-callback-${randomBytes(8).toString('hex')}`,
);
public readonly onTerminalCreated = this.terminalCreatedEmitter.event;
constructor(
@inject(INodeBinaryProvider) pathProvider: INodeBinaryProvider,
@inject(ILogger) logger: ILogger,
@inject(FS) private readonly fs: FsPromises,
@inject(ISourcePathResolverFactory) pathResolverFactory: ISourcePathResolverFactory,
@inject(IPortLeaseTracker) portLeaseTracker: IPortLeaseTracker,
) {
super(pathProvider, logger, portLeaseTracker, pathResolverFactory);
}
/**
* Gets telemetry of the last-started process.
*/
public async getProcessTelemetry() {
try {
return JSON.parse(await this.fs.readFile(this.callbackFile, 'utf-8')) as IProcessTelemetry;
} catch {
return undefined;
}
}
/**
* @inheritdoc
*/
protected resolveParams(
params: AnyLaunchConfiguration,
): ITerminalLaunchConfiguration | undefined {
if (params.type === DebugType.Terminal && params.request === 'launch') {
return params;
}
if (params.type === DebugType.Chrome && params.server && 'command' in params.server) {
return params.server;
}
return undefined;
}
/**
* Launches the program.
*/
protected async launchProgram(runData: IRunData<ITerminalLaunchConfiguration>): Promise<void> {
// Make sure that, if we can _find_ a in their path, it's the right
// version so that we don't mysteriously never connect fail.
let binary: NodeBinary | undefined;
try {
binary = await this.resolveNodePath(runData.params);
} catch (err) {
if (err instanceof ProtocolError && err.cause.id === ErrorCodes.NodeBinaryOutOfDate) {
throw err;
} else {
binary = new NodeBinary('node', undefined);
}
}
const env = await this.resolveEnvironment(runData, binary, {
fileCallback: this.callbackFile,
});
const terminal = await this.createTerminal({
name: runData.params.name,
cwd: runData.params.cwd,
iconPath: new vscode.ThemeIcon('debug'),
env: hideDebugInfoFromConsole(binary, env).defined(),
isTransient: true,
});
this.terminalCreatedEmitter.fire(terminal);
terminal.show();
const program = (this.program = new VSCodeTerminalProcess(terminal));
if (runData.params.command) {
// Add wait for #1642
// "There's a known issue that processId can not resolve... to be safe could you have a race timeout"
await Promise.race([terminal.processId, delay(1000)]);
terminal.sendText(runData.params.command, true);
}
program.stopped.then(result => {
if (program === this.program) {
this.onProgramTerminated(result);
}
});
}
/**
* Creates a terminal with the requested options.
*/
protected createTerminal(options: vscode.TerminalOptions) {
return Promise.resolve(vscode.window.createTerminal(options));
}
/**
* @inheritdoc
*/
public dispose() {
super.dispose();
this.fs.unlink(this.callbackFile).catch(() => undefined);
}
}