Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -0,0 +1,545 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { exists } from 'vs/base/node/pfs';
import * as cp from 'child_process';
import * as stream from 'stream';
import * as nls from 'vs/nls';
import * as net from 'net';
import * as path from 'vs/base/common/path';
import * as strings from 'vs/base/common/strings';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { IDebugAdapter, IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
/**
* Abstract implementation of the low level API for a debug adapter.
* Missing is how this API communicates with the debug adapter.
*/
export abstract class AbstractDebugAdapter implements IDebugAdapter {
private sequence: number;
private pendingRequests: Map<number, (e: DebugProtocol.Response) => void>;
private requestCallback: (request: DebugProtocol.Request) => void;
private eventCallback: (request: DebugProtocol.Event) => void;
private messageCallback: (message: DebugProtocol.ProtocolMessage) => void;
protected readonly _onError: Emitter<Error>;
protected readonly _onExit: Emitter<number | null>;
constructor() {
this.sequence = 1;
this.pendingRequests = new Map();
this._onError = new Emitter<Error>();
this._onExit = new Emitter<number>();
}
abstract startSession(): Promise<void>;
abstract stopSession(): Promise<void>;
abstract sendMessage(message: DebugProtocol.ProtocolMessage): void;
get onError(): Event<Error> {
return this._onError.event;
}
get onExit(): Event<number | null> {
return this._onExit.event;
}
onMessage(callback: (message: DebugProtocol.ProtocolMessage) => void): void {
if (this.eventCallback) {
this._onError.fire(new Error(`attempt to set more than one 'Message' callback`));
}
this.messageCallback = callback;
}
onEvent(callback: (event: DebugProtocol.Event) => void): void {
if (this.eventCallback) {
this._onError.fire(new Error(`attempt to set more than one 'Event' callback`));
}
this.eventCallback = callback;
}
onRequest(callback: (request: DebugProtocol.Request) => void): void {
if (this.requestCallback) {
this._onError.fire(new Error(`attempt to set more than one 'Request' callback`));
}
this.requestCallback = callback;
}
sendResponse(response: DebugProtocol.Response): void {
if (response.seq > 0) {
this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`));
} else {
this.internalSend('response', response);
}
}
sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): void {
const request: any = {
command: command
};
if (args && Object.keys(args).length > 0) {
request.arguments = args;
}
this.internalSend('request', request);
if (typeof timeout === 'number') {
const timer = setTimeout(() => {
clearTimeout(timer);
const clb = this.pendingRequests.get(request.seq);
if (clb) {
this.pendingRequests.delete(request.seq);
const err: DebugProtocol.Response = {
type: 'response',
seq: 0,
request_seq: request.seq,
success: false,
command,
message: `timeout after ${timeout} ms`
};
clb(err);
}
}, timeout);
}
if (clb) {
// store callback for this request
this.pendingRequests.set(request.seq, clb);
}
}
acceptMessage(message: DebugProtocol.ProtocolMessage): void {
if (this.messageCallback) {
this.messageCallback(message);
} else {
switch (message.type) {
case 'event':
if (this.eventCallback) {
this.eventCallback(<DebugProtocol.Event>message);
}
break;
case 'request':
if (this.requestCallback) {
this.requestCallback(<DebugProtocol.Request>message);
}
break;
case 'response':
const response = <DebugProtocol.Response>message;
const clb = this.pendingRequests.get(response.request_seq);
if (clb) {
this.pendingRequests.delete(response.request_seq);
clb(response);
}
break;
}
}
}
private internalSend(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void {
message.type = typ;
message.seq = this.sequence++;
this.sendMessage(message);
}
protected cancelPending() {
const pending = this.pendingRequests;
this.pendingRequests = new Map();
setTimeout(_ => {
pending.forEach((callback, request_seq) => {
const err: DebugProtocol.Response = {
type: 'response',
seq: 0,
request_seq,
success: false,
command: 'canceled',
message: 'canceled'
};
callback(err);
});
}, 1000);
}
dispose(): void {
this.cancelPending();
}
}
/**
* An implementation that communicates via two streams with the debug adapter.
*/
export abstract class StreamDebugAdapter extends AbstractDebugAdapter {
private static readonly TWO_CRLF = '\r\n\r\n';
private static readonly HEADER_LINESEPARATOR = /\r?\n/; // allow for non-RFC 2822 conforming line separators
private static readonly HEADER_FIELDSEPARATOR = /: */;
private outputStream: stream.Writable;
private rawData: Buffer;
private contentLength: number;
constructor() {
super();
}
protected connect(readable: stream.Readable, writable: stream.Writable): void {
this.outputStream = writable;
this.rawData = Buffer.allocUnsafe(0);
this.contentLength = -1;
readable.on('data', (data: Buffer) => this.handleData(data));
}
sendMessage(message: DebugProtocol.ProtocolMessage): void {
if (this.outputStream) {
const json = JSON.stringify(message);
this.outputStream.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}${StreamDebugAdapter.TWO_CRLF}${json}`, 'utf8');
}
}
private handleData(data: Buffer): void {
this.rawData = Buffer.concat([this.rawData, data]);
while (true) {
if (this.contentLength >= 0) {
if (this.rawData.length >= this.contentLength) {
const message = this.rawData.toString('utf8', 0, this.contentLength);
this.rawData = this.rawData.slice(this.contentLength);
this.contentLength = -1;
if (message.length > 0) {
try {
this.acceptMessage(<DebugProtocol.ProtocolMessage>JSON.parse(message));
} catch (e) {
this._onError.fire(new Error((e.message || e) + '\n' + message));
}
}
continue; // there may be more complete messages to process
}
} else {
const idx = this.rawData.indexOf(StreamDebugAdapter.TWO_CRLF);
if (idx !== -1) {
const header = this.rawData.toString('utf8', 0, idx);
const lines = header.split(StreamDebugAdapter.HEADER_LINESEPARATOR);
for (const h of lines) {
const kvPair = h.split(StreamDebugAdapter.HEADER_FIELDSEPARATOR);
if (kvPair[0] === 'Content-Length') {
this.contentLength = Number(kvPair[1]);
}
}
this.rawData = this.rawData.slice(idx + StreamDebugAdapter.TWO_CRLF.length);
continue;
}
}
break;
}
}
}
/**
* An implementation that connects to a debug adapter via a socket.
*/
export class SocketDebugAdapter extends StreamDebugAdapter {
private socket?: net.Socket;
constructor(private adapterServer: IDebugAdapterServer) {
super();
}
startSession(): Promise<void> {
return new Promise<void>((resolve, reject) => {
let connected = false;
this.socket = net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', () => {
this.connect(this.socket!, this.socket!);
resolve();
connected = true;
});
this.socket.on('close', () => {
if (connected) {
this._onError.fire(new Error('connection closed'));
} else {
reject(new Error('connection closed'));
}
});
this.socket.on('error', error => {
if (connected) {
this._onError.fire(error);
} else {
reject(error);
}
});
});
}
stopSession(): Promise<void> {
// Cancel all sent promises on disconnect so debug trees are not left in a broken state #3666.
this.cancelPending();
if (this.socket) {
this.socket.end();
this.socket = undefined;
}
return Promise.resolve(undefined);
}
}
/**
* An implementation that launches the debug adapter as a separate process and communicates via stdin/stdout.
*/
export class ExecutableDebugAdapter extends StreamDebugAdapter {
private serverProcess: cp.ChildProcess;
constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string, private readonly outputService?: IOutputService) {
super();
}
async startSession(): Promise<void> {
const command = this.adapterExecutable.command;
const args = this.adapterExecutable.args;
const options = this.adapterExecutable.options || {};
try {
// verify executables asynchronously
if (command) {
if (path.isAbsolute(command)) {
const commandExists = await exists(command);
if (!commandExists) {
throw new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", command));
}
} else {
// relative path
if (command.indexOf('/') < 0 && command.indexOf('\\') < 0) {
// no separators: command looks like a runtime name like 'node' or 'mono'
// TODO: check that the runtime is available on PATH
}
}
} else {
throw new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] },
"Cannot determine executable for debug adapter '{0}'.", this.debugType));
}
let env = objects.mixin({}, process.env);
if (options.env) {
env = objects.mixin(env, options.env);
}
delete env.VSCODE_PREVENT_FOREIGN_INSPECT;
if (command === 'node') {
if (Array.isArray(args) && args.length > 0) {
const isElectron = !!process.env['ELECTRON_RUN_AS_NODE'] || !!process.versions['electron'];
const forkOptions: cp.ForkOptions = {
env: env,
execArgv: isElectron ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] : [],
silent: true
};
if (options.cwd) {
forkOptions.cwd = options.cwd;
}
const child = cp.fork(args[0], args.slice(1), forkOptions);
if (!child.pid) {
throw new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", args[0]));
}
this.serverProcess = child;
} else {
throw new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter."));
}
} else {
const spawnOptions: cp.SpawnOptions = {
env: env
};
if (options.cwd) {
spawnOptions.cwd = options.cwd;
}
this.serverProcess = cp.spawn(command, args, spawnOptions);
}
this.serverProcess.on('error', err => {
this._onError.fire(err);
});
this.serverProcess.on('exit', (code, signal) => {
this._onExit.fire(code);
});
this.serverProcess.stdout.on('close', () => {
this._onError.fire(new Error('read error'));
});
this.serverProcess.stdout.on('error', error => {
this._onError.fire(error);
});
this.serverProcess.stdin.on('error', error => {
this._onError.fire(error);
});
const outputService = this.outputService;
if (outputService) {
const sanitize = (s: string) => s.toString().replace(/\r?\n$/mg, '');
// this.serverProcess.stdout.on('data', (data: string) => {
// console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;');
// });
this.serverProcess.stderr.on('data', (data: string) => {
const channel = outputService.getChannel(ExtensionsChannelId);
if (channel) {
channel.append(sanitize(data));
}
});
}
// finally connect to the DA
this.connect(this.serverProcess.stdout, this.serverProcess.stdin);
} catch (err) {
this._onError.fire(err);
}
}
stopSession(): Promise<void> {
// Cancel all sent promises on disconnect so debug trees are not left in a broken state #3666.
this.cancelPending();
if (!this.serverProcess) {
return Promise.resolve(undefined);
}
// when killing a process in windows its child
// processes are *not* killed but become root
// processes. Therefore we use TASKKILL.EXE
if (platform.isWindows) {
return new Promise<void>((c, e) => {
const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) {
if (err) {
return e(err);
}
});
killer.on('exit', c);
killer.on('error', e);
});
} else {
this.serverProcess.kill('SIGTERM');
return Promise.resolve(undefined);
}
}
private static extract(platformContribution: IPlatformSpecificAdapterContribution, extensionFolderPath: string): IDebuggerContribution | undefined {
if (!platformContribution) {
return undefined;
}
const result: IDebuggerContribution = Object.create(null);
if (platformContribution.runtime) {
if (platformContribution.runtime.indexOf('./') === 0) { // TODO
result.runtime = path.join(extensionFolderPath, platformContribution.runtime);
} else {
result.runtime = platformContribution.runtime;
}
}
if (platformContribution.runtimeArgs) {
result.runtimeArgs = platformContribution.runtimeArgs;
}
if (platformContribution.program) {
if (!path.isAbsolute(platformContribution.program)) {
result.program = path.join(extensionFolderPath, platformContribution.program);
} else {
result.program = platformContribution.program;
}
}
if (platformContribution.args) {
result.args = platformContribution.args;
}
const contribution = platformContribution as IDebuggerContribution;
if (contribution.win) {
result.win = ExecutableDebugAdapter.extract(contribution.win, extensionFolderPath);
}
if (contribution.winx86) {
result.winx86 = ExecutableDebugAdapter.extract(contribution.winx86, extensionFolderPath);
}
if (contribution.windows) {
result.windows = ExecutableDebugAdapter.extract(contribution.windows, extensionFolderPath);
}
if (contribution.osx) {
result.osx = ExecutableDebugAdapter.extract(contribution.osx, extensionFolderPath);
}
if (contribution.linux) {
result.linux = ExecutableDebugAdapter.extract(contribution.linux, extensionFolderPath);
}
return result;
}
static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable | undefined {
let result: IDebuggerContribution = Object.create(null);
debugType = debugType.toLowerCase();
// merge all contributions into one
for (const ed of extensionDescriptions) {
if (ed.contributes) {
const debuggers = <IDebuggerContribution[]>ed.contributes['debuggers'];
if (debuggers && debuggers.length > 0) {
debuggers.filter(dbg => typeof dbg.type === 'string' && strings.equalsIgnoreCase(dbg.type, debugType)).forEach(dbg => {
// extract relevant attributes and make them absolute where needed
const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath);
// merge
result = objects.mixin(result, extractedDbg, ed.isBuiltin);
});
}
}
}
// select the right platform
let platformInfo: IPlatformSpecificAdapterContribution | undefined;
if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) {
platformInfo = result.winx86 || result.win || result.windows;
} else if (platform.isWindows) {
platformInfo = result.win || result.windows;
} else if (platform.isMacintosh) {
platformInfo = result.osx;
} else if (platform.isLinux) {
platformInfo = result.linux;
}
platformInfo = platformInfo || result;
// these are the relevant attributes
let program = platformInfo.program || result.program;
const args = platformInfo.args || result.args;
let runtime = platformInfo.runtime || result.runtime;
const runtimeArgs = platformInfo.runtimeArgs || result.runtimeArgs;
if (runtime) {
return {
type: 'executable',
command: runtime,
args: (runtimeArgs || []).concat(typeof program === 'string' ? [program] : []).concat(args || [])
};
} else if (program) {
return {
type: 'executable',
command: program,
args: args || []
};
}
// nothing found
return undefined;
}
}

View File

@@ -0,0 +1,381 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp';
import * as strings from 'vs/base/common/strings';
import * as objects from 'vs/base/common/objects';
import { isObject } from 'vs/base/common/types';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { memoize } from 'vs/base/common/decorators';
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
export class Debugger implements IDebugger {
private debuggerContribution: IDebuggerContribution;
private mergedExtensionDescriptions: IExtensionDescription[] = [];
private mainExtensionDescription: IExtensionDescription | undefined;
constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService,
@ICommandService private readonly commandService: ICommandService,
@IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
this.debuggerContribution = { type: dbgContribution.type };
this.merge(dbgContribution, extensionDescription);
}
public merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void {
/**
* Copies all properties of source into destination. The optional parameter "overwrite" allows to control
* if existing non-structured properties on the destination should be overwritten or not. Defaults to true (overwrite).
*/
function mixin(destination: any, source: any, overwrite: boolean, level = 0): any {
if (!isObject(destination)) {
return source;
}
if (isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(destination[key]) && isObject(source[key])) {
mixin(destination[key], source[key], overwrite, level + 1);
} else {
if (key in destination) {
if (overwrite) {
if (level === 0 && key === 'type') {
// don't merge the 'type' property
} else {
destination[key] = source[key];
}
}
} else {
destination[key] = source[key];
}
}
});
}
return destination;
}
// only if not already merged
if (this.mergedExtensionDescriptions.indexOf(extensionDescription) < 0) {
// remember all extensions that have been merged for this debugger
this.mergedExtensionDescriptions.push(extensionDescription);
// merge new debugger contribution into existing contributions (and don't overwrite values in built-in extensions)
mixin(this.debuggerContribution, otherDebuggerContribution, extensionDescription.isBuiltin);
// remember the extension that is considered the "main" debugger contribution
if (isDebuggerMainContribution(otherDebuggerContribution)) {
this.mainExtensionDescription = extensionDescription;
}
}
}
public createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter> {
return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => {
if (this.inExtHost()) {
const da = this.configurationManager.createDebugAdapter(session);
if (da) {
return Promise.resolve(da);
}
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
} else {
return this.getAdapterDescriptor(session).then(adapterDescriptor => {
switch (adapterDescriptor.type) {
case 'executable':
return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService);
case 'server':
return new SocketDebugAdapter(adapterDescriptor);
case 'implementation':
// TODO@AW: this.inExtHost() should now return true
return Promise.resolve(this.configurationManager.createDebugAdapter(session));
default:
throw new Error('unknown descriptor type');
}
}).catch(err => {
if (err && err.message) {
throw new Error(nls.localize('cannot.create.da.with.err', "Cannot create debug adapter ({0}).", err.message));
} else {
throw new Error(nls.localize('cannot.create.da', "Cannot create debug adapter."));
}
});
}
});
}
private getAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor> {
// a "debugServer" attribute in the launch config takes precedence
if (typeof session.configuration.debugServer === 'number') {
return Promise.resolve(<IDebugAdapterServer>{
type: 'server',
port: session.configuration.debugServer
});
}
// try the new "createDebugAdapterDescriptor" and the deprecated "provideDebugAdapter" API
return this.configurationManager.getDebugAdapterDescriptor(session).then(adapter => {
if (adapter) {
return adapter;
}
// try deprecated command based extension API "adapterExecutableCommand" to determine the executable
if (this.debuggerContribution.adapterExecutableCommand) {
console.info('debugAdapterExecutable attribute in package.json is deprecated and support for it will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.');
const rootFolder = session.root ? session.root.uri.toString() : undefined;
return this.commandService.executeCommand<IDebugAdapterExecutable>(this.debuggerContribution.adapterExecutableCommand, rootFolder).then(ae => {
if (ae) {
return <IAdapterDescriptor>{
type: 'executable',
command: ae.command,
args: ae.args || []
};
}
throw new Error('command adapterExecutableCommand did not return proper command.');
});
}
// fallback: use executable information from package.json
const ae = ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type);
if (ae === undefined) {
throw new Error('no executable specified in package.json');
}
return ae;
});
}
substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
if (this.inExtHost()) {
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
});
} else {
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
}
}
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
const config = this.configurationService.getValue<ITerminalSettings>('terminal');
return this.configurationManager.runInTerminal(this.inExtHost() ? this.type : '*', args, config);
}
private inExtHost(): boolean {
/*
const debugConfigs = this.configurationService.getValue<IDebugConfiguration>('debug');
return !!debugConfigs.extensionHostDebugAdapter
|| this.configurationManager.needsToRunInExtHost(this.type)
|| (!!this.mainExtensionDescription && this.mainExtensionDescription.extensionLocation.scheme !== 'file');
*/
return true;
}
get label(): string {
return this.debuggerContribution.label || this.debuggerContribution.type;
}
get type(): string {
return this.debuggerContribution.type;
}
get variables(): { [key: string]: string } | undefined {
return this.debuggerContribution.variables;
}
get configurationSnippets(): IJSONSchemaSnippet[] | undefined {
return this.debuggerContribution.configurationSnippets;
}
get languages(): string[] | undefined {
return this.debuggerContribution.languages;
}
hasInitialConfiguration(): boolean {
return !!this.debuggerContribution.initialConfigurations;
}
hasConfigurationProvider(): boolean {
return this.configurationManager.hasDebugConfigurationProvider(this.type);
}
getInitialConfigurationContent(initialConfigs?: IConfig[]): Promise<string> {
// at this point we got some configs from the package.json and/or from registered DebugConfigurationProviders
let initialConfigurations = this.debuggerContribution.initialConfigurations || [];
if (initialConfigs) {
initialConfigurations = initialConfigurations.concat(initialConfigs);
}
const eol = this.resourcePropertiesService.getEOL(URI.from({ scheme: Schemas.untitled, path: '1' })) === '\r\n' ? '\r\n' : '\n';
const configs = JSON.stringify(initialConfigurations, null, '\t').split('\n').map(line => '\t' + line).join(eol).trim();
const comment1 = nls.localize('launch.config.comment1', "Use IntelliSense to learn about possible attributes.");
const comment2 = nls.localize('launch.config.comment2', "Hover to view descriptions of existing attributes.");
const comment3 = nls.localize('launch.config.comment3', "For more information, visit: {0}", 'https://go.microsoft.com/fwlink/?linkid=830387');
let content = [
'{',
`\t// ${comment1}`,
`\t// ${comment2}`,
`\t// ${comment3}`,
`\t"version": "0.2.0",`,
`\t"configurations": ${configs}`,
'}'
].join(eol);
// fix formatting
const editorConfig = this.configurationService.getValue<any>();
if (editorConfig.editor && editorConfig.editor.insertSpaces) {
content = content.replace(new RegExp('\t', 'g'), strings.repeat(' ', editorConfig.editor.tabSize));
}
return Promise.resolve(content);
}
public getMainExtensionDescriptor(): IExtensionDescription {
return this.mainExtensionDescription || this.mergedExtensionDescriptions[0];
}
@memoize
getCustomTelemetryService(): Promise<TelemetryService | undefined> {
const aiKey = this.debuggerContribution.aiKey;
if (!aiKey) {
return Promise.resolve(undefined);
}
return this.telemetryService.getTelemetryInfo().then(info => {
const telemetryInfo: { [key: string]: string } = Object.create(null);
telemetryInfo['common.vscodemachineid'] = info.machineId;
telemetryInfo['common.vscodesessionid'] = info.sessionId;
return telemetryInfo;
}).then(data => {
const client = new TelemetryClient(
getPathFromAmdModule(require, 'bootstrap-fork'),
{
serverName: 'Debug Telemetry',
timeout: 1000 * 60 * 5,
args: [`${this.getMainExtensionDescriptor().publisher}.${this.type}`, JSON.stringify(data), aiKey],
env: {
ELECTRON_RUN_AS_NODE: 1,
PIPE_LOGGING: 'true',
AMD_ENTRYPOINT: 'vs/workbench/contrib/debug/node/telemetryApp'
}
}
);
const channel = client.getChannel('telemetryAppender');
const appender = new TelemetryAppenderClient(channel);
return new TelemetryService({ appender }, this.configurationService);
});
}
getSchemaAttributes(): IJSONSchema[] | null {
if (!this.debuggerContribution.configurationAttributes) {
return null;
}
// fill in the default configuration attributes shared by all adapters.
const taskSchema = TaskDefinitionRegistry.getJsonSchema();
return Object.keys(this.debuggerContribution.configurationAttributes).map(request => {
const attributes: IJSONSchema = this.debuggerContribution.configurationAttributes[request];
const defaultRequired = ['name', 'type', 'request'];
attributes.required = attributes.required && attributes.required.length ? defaultRequired.concat(attributes.required) : defaultRequired;
attributes.additionalProperties = false;
attributes.type = 'object';
if (!attributes.properties) {
attributes.properties = {};
}
const properties = attributes.properties;
properties['type'] = {
enum: [this.type],
description: nls.localize('debugType', "Type of configuration."),
pattern: '^(?!node2)',
errorMessage: nls.localize('debugTypeNotRecognised', "The debug type is not recognized. Make sure that you have a corresponding debug extension installed and that it is enabled."),
patternErrorMessage: nls.localize('node2NotSupported', "\"node2\" is no longer supported, use \"node\" instead and set the \"protocol\" attribute to \"inspector\".")
};
properties['name'] = {
type: 'string',
description: nls.localize('debugName', "Name of configuration; appears in the launch configuration drop down menu."),
default: 'Launch'
};
properties['request'] = {
enum: [request],
description: nls.localize('debugRequest', "Request type of configuration. Can be \"launch\" or \"attach\"."),
};
properties['debugServer'] = {
type: 'number',
description: nls.localize('debugServer', "For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode"),
default: 4711
};
properties['preLaunchTask'] = {
anyOf: [taskSchema, {
type: ['string', 'null'],
}],
default: '',
description: nls.localize('debugPrelaunchTask', "Task to run before debug session starts.")
};
properties['postDebugTask'] = {
anyOf: [taskSchema, {
type: ['string', 'null'],
}],
default: '',
description: nls.localize('debugPostDebugTask', "Task to run after debug session ends.")
};
properties['internalConsoleOptions'] = INTERNAL_CONSOLE_OPTIONS_SCHEMA;
// Clear out windows, linux and osx fields to not have cycles inside the properties object
delete properties['windows'];
delete properties['osx'];
delete properties['linux'];
const osProperties = objects.deepClone(properties);
properties['windows'] = {
type: 'object',
description: nls.localize('debugWindowsConfiguration', "Windows specific launch configuration attributes."),
properties: osProperties
};
properties['osx'] = {
type: 'object',
description: nls.localize('debugOSXConfiguration', "OS X specific launch configuration attributes."),
properties: osProperties
};
properties['linux'] = {
type: 'object',
description: nls.localize('debugLinuxConfiguration', "Linux specific launch configuration attributes."),
properties: osProperties
};
Object.keys(properties).forEach(name => {
// Use schema allOf property to get independent error reporting #21113
ConfigurationResolverUtils.applyDeprecatedVariableMessage(properties[name]);
});
return attributes;
});
}
}

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc';
const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
process.once('exit', () => appender.dispose());
const channel = new TelemetryAppenderChannel(appender);
const server = new Server('telemetry');
server.registerChannel('telemetryAppender', channel);

View File

@@ -0,0 +1,421 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import * as nls from 'vs/nls';
import * as env from 'vs/base/common/platform';
import * as pfs from 'vs/base/node/pfs';
import { assign } from 'vs/base/common/objects';
import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug';
import { getPathFromAmdModule } from 'vs/base/common/amd';
const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
let terminalLauncher: ITerminalLauncher | undefined = undefined;
export function getTerminalLauncher() {
if (!terminalLauncher) {
if (env.isWindows) {
terminalLauncher = new WinTerminalService();
} else if (env.isMacintosh) {
terminalLauncher = new MacTerminalService();
} else if (env.isLinux) {
terminalLauncher = new LinuxTerminalService();
}
}
return terminalLauncher;
}
let _DEFAULT_TERMINAL_LINUX_READY: Promise<string> | null = null;
export function getDefaultTerminalLinuxReady(): Promise<string> {
if (!_DEFAULT_TERMINAL_LINUX_READY) {
_DEFAULT_TERMINAL_LINUX_READY = new Promise<string>(c => {
if (env.isLinux) {
Promise.all<any>([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => {
if (isDebian) {
c('x-terminal-emulator');
} else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') {
c('gnome-terminal');
} else if (process.env.DESKTOP_SESSION === 'kde-plasma') {
c('konsole');
} else if (process.env.COLORTERM) {
c(process.env.COLORTERM);
} else if (process.env.TERM) {
c(process.env.TERM);
} else {
c('xterm');
}
});
return;
}
c('xterm');
});
}
return _DEFAULT_TERMINAL_LINUX_READY;
}
let _DEFAULT_TERMINAL_WINDOWS: string | null = null;
export function getDefaultTerminalWindows(): string {
if (!_DEFAULT_TERMINAL_WINDOWS) {
const isWoW64 = !!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
_DEFAULT_TERMINAL_WINDOWS = `${process.env.windir ? process.env.windir : 'C:\\Windows'}\\${isWoW64 ? 'Sysnative' : 'System32'}\\cmd.exe`;
}
return _DEFAULT_TERMINAL_WINDOWS;
}
abstract class TerminalLauncher implements ITerminalLauncher {
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<number | undefined> {
return this.runInTerminal0(args.title!, args.cwd, args.args, args.env || {}, config);
}
abstract runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment | {}, config): Promise<number | undefined>;
}
class WinTerminalService extends TerminalLauncher {
private static readonly CMD = 'cmd.exe';
runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise<number | undefined> {
const exec = configuration.external.windowsExec || getDefaultTerminalWindows();
return new Promise<number | undefined>((c, e) => {
const title = `"${dir} - ${TERMINAL_TITLE}"`;
const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code
const cmdArgs = [
'/c', 'start', title, '/wait', exec, '/c', command
];
// merge environment variables into a copy of the process.env
const env = assign({}, process.env, envVars);
// delete environment variables that have a null value
Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
const options: any = {
cwd: dir,
env: env,
windowsVerbatimArguments: true
};
const cmd = cp.spawn(WinTerminalService.CMD, cmdArgs, options);
cmd.on('error', e);
c(undefined);
});
}
}
class MacTerminalService extends TerminalLauncher {
private static readonly DEFAULT_TERMINAL_OSX = 'Terminal.app';
private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X
runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise<number | undefined> {
const terminalApp = configuration.external.osxExec || MacTerminalService.DEFAULT_TERMINAL_OSX;
return new Promise<number | undefined>((c, e) => {
if (terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') {
// On OS X we launch an AppleScript that creates (or reuses) a Terminal window
// and then launches the program inside that window.
const script = terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper';
const scriptpath = getPathFromAmdModule(require, `vs/workbench/contrib/externalTerminal/electron-browser/${script}.scpt`);
const osaArgs = [
scriptpath,
'-t', title || TERMINAL_TITLE,
'-w', dir,
];
for (let a of args) {
osaArgs.push('-a');
osaArgs.push(a);
}
if (envVars) {
for (let key in envVars) {
const value = envVars[key];
if (value === null) {
osaArgs.push('-u');
osaArgs.push(key);
} else {
osaArgs.push('-e');
osaArgs.push(`${key}=${value}`);
}
}
}
let stderr = '';
const osa = cp.spawn(MacTerminalService.OSASCRIPT, osaArgs);
osa.on('error', e);
osa.stderr.on('data', (data) => {
stderr += data.toString();
});
osa.on('exit', (code: number) => {
if (code === 0) { // OK
c(undefined);
} else {
if (stderr) {
const lines = stderr.split('\n', 1);
e(new Error(lines[0]));
} else {
e(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code)));
}
}
});
} else {
e(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp)));
}
});
}
}
class LinuxTerminalService extends TerminalLauncher {
private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue...");
runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise<number | undefined> {
const terminalConfig = configuration.external;
const execThenable: Promise<string> = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady();
return new Promise<number | undefined>((c, e) => {
let termArgs: string[] = [];
//termArgs.push('--title');
//termArgs.push(`"${TERMINAL_TITLE}"`);
execThenable.then(exec => {
if (exec.indexOf('gnome-terminal') >= 0) {
termArgs.push('-x');
} else {
termArgs.push('-e');
}
termArgs.push('bash');
termArgs.push('-c');
const bashCommand = `${quote(args)}; echo; read -p "${LinuxTerminalService.WAIT_MESSAGE}" -n1;`;
termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set...
// merge environment variables into a copy of the process.env
const env = assign({}, process.env, envVars);
// delete environment variables that have a null value
Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
const options: any = {
cwd: dir,
env: env
};
let stderr = '';
const cmd = cp.spawn(exec, termArgs, options);
cmd.on('error', e);
cmd.stderr.on('data', (data) => {
stderr += data.toString();
});
cmd.on('exit', (code: number) => {
if (code === 0) { // OK
c(undefined);
} else {
if (stderr) {
const lines = stderr.split('\n', 1);
e(new Error(lines[0]));
} else {
e(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code)));
}
}
});
});
});
}
}
/**
* Quote args if necessary and combine into a space separated string.
*/
function quote(args: string[]): string {
let r = '';
for (let a of args) {
if (a.indexOf(' ') >= 0) {
r += '"' + a + '"';
} else {
r += a;
}
r += ' ';
}
return r;
}
export function hasChildProcesses(processId: number): boolean {
if (processId) {
try {
// if shell has at least one child process, assume that shell is busy
if (env.isWindows) {
const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']);
if (result.stdout) {
const pids = result.stdout.toString().split('\r\n');
if (!pids.some(p => parseInt(p) === processId)) {
return false;
}
}
} else {
const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(processId)]);
if (result.stdout) {
const r = result.stdout.toString().trim();
if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683
return false;
}
}
}
}
catch (e) {
// silently ignore
}
}
// fall back to safe side
return true;
}
const enum ShellType { cmd, powershell, bash }
export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): string {
let shellType: ShellType;
// get the shell configuration for the current platform
let shell: string;
const shell_config = config.integrated.shell;
if (env.isWindows) {
shell = shell_config.windows;
shellType = ShellType.cmd;
} else if (env.isLinux) {
shell = shell_config.linux;
shellType = ShellType.bash;
} else if (env.isMacintosh) {
shell = shell_config.osx;
shellType = ShellType.bash;
} else {
throw new Error('Unknown platform');
}
// try to determine the shell type
shell = shell.trim().toLowerCase();
if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0) {
shellType = ShellType.powershell;
} else if (shell.indexOf('cmd.exe') >= 0) {
shellType = ShellType.cmd;
} else if (shell.indexOf('bash') >= 0) {
shellType = ShellType.bash;
} else if (shell.indexOf('git\\bin\\bash.exe') >= 0) {
shellType = ShellType.bash;
}
let quote: (s: string) => string;
let command = '';
switch (shellType) {
case ShellType.powershell:
quote = (s: string) => {
s = s.replace(/\'/g, '\'\'');
return `'${s}'`;
//return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
};
if (args.cwd) {
command += `cd '${args.cwd}'; `;
}
if (args.env) {
for (let key in args.env) {
const value = args.env[key];
if (value === null) {
command += `Remove-Item env:${key}; `;
} else {
command += `\${env:${key}}='${value}'; `;
}
}
}
if (args.args && args.args.length > 0) {
const cmd = quote(args.args.shift()!);
command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `;
for (let a of args.args) {
command += `${quote(a)} `;
}
}
break;
case ShellType.cmd:
quote = (s: string) => {
s = s.replace(/\"/g, '""');
return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0) ? `"${s}"` : s;
};
if (args.cwd) {
command += `cd ${quote(args.cwd)} && `;
}
if (args.env) {
command += 'cmd /C "';
for (let key in args.env) {
let value = args.env[key];
if (value === null) {
command += `set "${key}=" && `;
} else {
value = value.replace(/[\^\&]/g, s => `^${s}`);
command += `set "${key}=${value}" && `;
}
}
}
for (let a of args.args) {
command += `${quote(a)} `;
}
if (args.env) {
command += '"';
}
break;
case ShellType.bash:
quote = (s: string) => {
s = s.replace(/\"/g, '\\"');
return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s;
};
const hardQuote = (s: string) => {
return /[^\w@%\/+=,.:^-]/.test(s) ? `'${s.replace(/'/g, '\'\\\'\'')}'` : s;
};
if (args.cwd) {
command += `cd ${quote(args.cwd)} ; `;
}
if (args.env) {
command += 'env';
for (let key in args.env) {
const value = args.env[key];
if (value === null) {
command += ` -u ${hardQuote(key)}`;
} else {
command += ` ${hardQuote(`${key}=${value}`)}`;
}
}
command += ' ';
}
for (let a of args.args) {
command += `${quote(a)} `;
}
break;
}
return command;
}