未验证 提交 fb208416 编写于 作者: I Isidor Nikolic 提交者: GitHub

Merge pull request #41818 from Microsoft/isidorn/multiRootLaunch

support Compound debug configurations across workspace folders 
......@@ -158,7 +158,7 @@ export class StartDebugActionItem implements IActionItem {
if (name === manager.selectedName && launch === manager.selectedLaunch) {
this.selected = this.options.length;
}
const label = launches.length > 1 ? `${name} (${launch.workspace.name})` : name;
const label = launches.length > 1 ? `${name} (${launch.name})` : name;
this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } });
}));
......@@ -169,10 +169,10 @@ export class StartDebugActionItem implements IActionItem {
const disabledIdx = this.options.length - 1;
launches.forEach(l => {
const label = launches.length > 1 ? nls.localize("addConfigTo", "Add Config ({0})...", l.workspace.name) : nls.localize('addConfiguration', "Add Configuration...");
const label = launches.length > 1 ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
this.options.push({
label, handler: () => {
this.commandService.executeCommand('debug.addConfiguration', l.workspace.uri.toString()).done(undefined, errors.onUnexpectedError);
this.commandService.executeCommand('debug.addConfiguration', l.uri.toString()).done(undefined, errors.onUnexpectedError);
return false;
}
});
......
......@@ -142,7 +142,7 @@ export class StartAction extends AbstractDebugAction {
if (contextService && contextService.getWorkbenchState() === WorkbenchState.EMPTY && processes.length > 0) {
return false;
}
if (processes.some(p => p.getName(false) === configName && (!launch || p.session.root.uri.toString() === launch.workspace.uri.toString()))) {
if (processes.some(p => p.getName(false) === configName && (!launch || p.session.root.uri.toString() === launch.uri.toString()))) {
return false;
}
const compound = launch && launch.getCompound(configName);
......
......@@ -29,7 +29,7 @@ class AddConfigEntry extends Model.QuickOpenEntry {
}
public getDescription(): string {
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.workspace.name : '';
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : '';
}
public getAriaLabel(): string {
......@@ -40,7 +40,7 @@ class AddConfigEntry extends Model.QuickOpenEntry {
if (mode === QuickOpen.Mode.PREVIEW) {
return false;
}
this.commandService.executeCommand('debug.addConfiguration', this.launch.workspace.uri.toString()).done(undefined, errors.onUnexpectedError);
this.commandService.executeCommand('debug.addConfiguration', this.launch.uri.toString()).done(undefined, errors.onUnexpectedError);
return true;
}
......@@ -57,7 +57,7 @@ class StartDebugEntry extends Model.QuickOpenEntry {
}
public getDescription(): string {
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.workspace.name : '';
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : '';
}
public getAriaLabel(): string {
......@@ -110,7 +110,7 @@ export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler {
});
}
launches.forEach((l, index) => {
const label = launches.length > 1 ? nls.localize("addConfigTo", "Add Config ({0})...", l.workspace.name) : nls.localize('addConfiguration', "Add Configuration...");
const label = launches.length > 1 ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, Filters.matchesContiguousSubString(input, label));
if (index === 0) {
configurations.push(new QuickOpenEntryGroup(entry, undefined, true));
......
......@@ -95,7 +95,7 @@ export class DebugStatus extends Themable implements IStatusbarItem {
if (manager.selectedName) {
const name = manager.selectedName;
this.statusBarItem.style.display = 'block';
this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedLaunch.workspace.name})` : name;
this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedLaunch.name})` : name;
} else {
this.statusBarItem.style.display = 'none';
}
......
......@@ -437,6 +437,11 @@ export interface ILaunch {
*/
uri: uri;
/**
* Name of the launch.
*/
name: string;
workspace: IWorkspaceFolder;
/**
......@@ -466,7 +471,7 @@ export interface ILaunch {
/**
* Opens the launch.json file. Creates if it does not exist.
*/
openConfigFile(sideBySide: boolean, type?: string): TPromise<{ editor: IEditor; configFileCreated: boolean; }>;
openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor>;
}
// Debug service interfaces
......
......@@ -169,11 +169,11 @@ export function registerCommands(): void {
accessor.get(IMessageService).show(severity.Info, nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
return TPromise.as(null);
}
const launch = manager.getLaunches().filter(l => l.workspace.uri.toString() === workspaceUri).pop() || manager.selectedLaunch;
const launch = manager.getLaunches().filter(l => l.uri.toString() === workspaceUri).pop() || manager.selectedLaunch;
return launch.openConfigFile(false).done(result => {
if (result.editor && !result.configFileCreated) {
const codeEditor = <ICodeEditor>result.editor.getControl();
return launch.openConfigFile(false).done(editor => {
if (editor) {
const codeEditor = <ICodeEditor>editor.getControl();
if (codeEditor) {
return codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
}
......
......@@ -9,6 +9,7 @@ import Event, { Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import * as strings from 'vs/base/common/strings';
import { first } from 'vs/base/common/arrays';
import severity from 'vs/base/common/severity';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import * as objects from 'vs/base/common/objects';
import uri from 'vs/base/common/uri';
......@@ -24,7 +25,7 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch } from 'vs/workbench/parts/debug/common/debug';
......@@ -33,6 +34,8 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { IMessageService } from 'vs/platform/message/common/message';
// debuggers extension point
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawAdapter[]>('debuggers', [], {
......@@ -147,14 +150,12 @@ const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtens
});
// debug general schema
export const schemaId = 'vscode://schemas/launch';
const defaultCompound: ICompound = { name: 'Compound', configurations: [] };
const schema: IJSONSchema = {
id: schemaId,
id: launchSchemaId,
type: 'object',
title: nls.localize('app.launch.json.title', "Launch"),
required: ['version', 'configurations'],
required: [],
default: { version: '0.2.0', configurations: [], compounds: [] },
properties: {
version: {
......@@ -201,7 +202,7 @@ const schema: IJSONSchema = {
};
const jsonRegistry = <IJSONContributionRegistry>Registry.as(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(schemaId, schema);
jsonRegistry.registerSchema(launchSchemaId, schema);
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
......@@ -232,7 +233,7 @@ export class ConfigurationManager implements IConfigurationManager {
this.registerListeners(lifecycleService);
this.initLaunches();
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
const filtered = this.launches.filter(l => l.workspace.uri.toString() === previousSelectedRoot);
const filtered = this.launches.filter(l => l.uri.toString() === previousSelectedRoot);
this.selectConfiguration(filtered.length ? filtered[0] : undefined, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
}
......@@ -335,6 +336,10 @@ export class ConfigurationManager implements IConfigurationManager {
private initLaunches(): void {
this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder));
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this));
}
if (this.launches.indexOf(this._selectedLaunch) === -1) {
this._selectedLaunch = undefined;
}
......@@ -356,6 +361,14 @@ export class ConfigurationManager implements IConfigurationManager {
return this._onDidSelectConfigurationName.event;
}
public getWorkspaceLaunch(): ILaunch {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
return this.launches[this.launches.length - 1];
}
return undefined;
}
public selectConfiguration(launch?: ILaunch, name?: string, debugStarted?: boolean): void {
const previousLaunch = this._selectedLaunch;
const previousName = this._selectedName;
......@@ -438,7 +451,7 @@ export class ConfigurationManager implements IConfigurationManager {
private store(): void {
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
if (this._selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this._selectedLaunch.workspace.uri.toString(), StorageScope.WORKSPACE);
this.storageService.store(DEBUG_SELECTED_ROOT, this._selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
}
}
......@@ -453,15 +466,28 @@ class Launch implements ILaunch {
private configurationManager: ConfigurationManager,
public workspace: IWorkspaceFolder,
@IFileService private fileService: IFileService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IConfigurationService protected configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
@IMessageService private messageService: IMessageService
) {
// noop
}
public get uri(): uri {
return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') });
}
public get name(): string {
return this.workspace.name;
}
protected getConfig(): IGlobalConfig {
return this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
}
public getCompound(name: string): ICompound {
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
const config = this.getConfig();
if (!config || !config.compounds) {
return null;
}
......@@ -470,7 +496,7 @@ class Launch implements ILaunch {
}
public getConfigurationNames(): string[] {
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
const config = this.getConfig();
if (!config || !config.configurations || !Array.isArray(config.configurations)) {
return [];
} else {
......@@ -487,7 +513,7 @@ class Launch implements ILaunch {
}
public getConfiguration(name: string): IConfig {
const config = objects.deepClone(this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri }));
const config = this.getConfig();
if (!config || !config.configurations) {
return null;
}
......@@ -518,15 +544,11 @@ class Launch implements ILaunch {
return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
}
public get uri(): uri {
return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') });
}
public openConfigFile(sideBySide: boolean, type?: string): TPromise<{ editor: IEditor; configFileCreated: boolean; }> {
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
const resource = this.uri;
let configFileCreated = false;
return this.fileService.resolveContent(resource).then(content => content, err => {
return this.fileService.resolveContent(resource).then(content => content.value, err => {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
......@@ -547,17 +569,17 @@ class Launch implements ILaunch {
configFileCreated = true;
return this.fileService.updateContent(resource, content).then(() => {
// convert string into IContent; see #32135
return { value: content };
return content;
});
});
}).then(content => {
if (!content) {
return { editor: undefined, configFileCreated };
return undefined;
}
const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`);
const index = content.indexOf(`"${this.configurationManager.selectedName}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (content.value.charAt(i) === '\n') {
if (content.charAt(i) === '\n') {
startLineNumber++;
}
}
......@@ -571,9 +593,46 @@ class Launch implements ILaunch {
pinned: configFileCreated, // pin only if config file is created #8727
revealIfVisible: true
},
}, sideBySide).then(editor => ({ editor, configFileCreated }));
}, sideBySide).then(editor => {
if (configFileCreated) {
this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application."));
}
return editor;
});
}, (error) => {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
});
}
}
class WorkspaceLaunch extends Launch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
@IFileService fileService: IFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
@IMessageService messageService: IMessageService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, messageService);
}
get uri(): uri {
return this.workspaceContextService.getWorkspace().configuration;
}
get name(): string {
return nls.localize('workspace', "workspace");
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch').workspace;
}
openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
return this.editorService.openEditor({ resource: this.workspaceContextService.getWorkspace().configuration });
}
}
......@@ -668,8 +668,8 @@ export class DebugService implements debug.IDebugService {
this.model.getBreakpoints().forEach(bp => bp.verified = false);
}
this.launchJsonChanged = false;
const manager = this.getConfigurationManager();
const launch = root ? manager.getLaunches().filter(l => l.workspace.uri.toString() === root.uri.toString()).pop() : undefined;
const launch = root ? this.configurationManager.getLaunches().filter(l => l.uri.toString() === root.uri.toString()).pop()
: this.configurationManager.getWorkspaceLaunch();
let config: debug.IConfig, compound: debug.ICompound;
if (!configOrName) {
......@@ -683,7 +683,7 @@ export class DebugService implements debug.IDebugService {
}
if (launch) {
// in the drop down the name of the top most compound takes precedence over the launch config name
manager.selectConfiguration(launch, topCompoundName || (typeof configOrName === 'string' ? configOrName : undefined), true);
this.configurationManager.selectConfiguration(launch, topCompoundName || (typeof configOrName === 'string' ? configOrName : undefined), true);
}
if (compound) {
......@@ -692,7 +692,22 @@ export class DebugService implements debug.IDebugService {
"Compound must have \"configurations\" attribute set in order to start multiple configurations.")));
}
return TPromise.join(compound.configurations.map(name => name !== compound.name ? this.startDebugging(root, name, noDebug, topCompoundName || compound.name) : TPromise.as(null)));
return TPromise.join(compound.configurations.map(name => {
if (name === compound.name) {
return TPromise.as(null);
}
let rootForName = root;
if (launch === this.configurationManager.getWorkspaceLaunch()) {
// For workspace launches allow comound referencing configurations across folder
const launchContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name)).pop();
if (launchContainingName) {
rootForName = launchContainingName.workspace;
}
}
return this.startDebugging(rootForName, name, noDebug, topCompoundName || compound.name);
}));
}
if (configOrName && !config) {
const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", configOrName) :
......@@ -723,7 +738,7 @@ export class DebugService implements debug.IDebugService {
return (type ? TPromise.as(null) : this.configurationManager.guessAdapter().then(a => type = a && a.type)).then(() =>
(type ? this.extensionService.activateByEvent(`onDebugResolve:${type}`) : TPromise.as(null)).then(() =>
this.configurationManager.resolveConfigurationByProviders(launch ? launch.workspace.uri : undefined, type, config).then(config => {
this.configurationManager.resolveConfigurationByProviders(launch ? launch.uri : undefined, type, config).then(config => {
// a falsy config indicates an aborted launch
if (config && config.type) {
return this.createProcess(root, config, sessionId);
......@@ -803,12 +818,7 @@ export class DebugService implements debug.IDebugService {
return undefined;
}
return this.configurationManager.selectedLaunch.openConfigFile(false).then(result => {
if (result.configFileCreated) {
this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application. {0}", err.message));
}
return undefined;
});
return this.configurationManager.selectedLaunch.openConfigFile(false).then(editor => void 0);
})
);
}
......
......@@ -23,10 +23,11 @@ export const defaultSettingsSchemaId = 'vscode://schemas/settings/default';
export const userSettingsSchemaId = 'vscode://schemas/settings/user';
export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace';
export const folderSettingsSchemaId = 'vscode://schemas/settings/folder';
export const launchSchemaId = 'vscode://schemas/launch';
export const TASKS_CONFIGURATION_KEY = 'tasks';
export const LAUNCH_CONFIGURATION_KEY = 'launch';
export const WORKSPACE_STANDALONE_CONFIGURATIONS = Object.create(null);
WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/tasks.json`;
WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/launch.json`;
\ No newline at end of file
WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/launch.json`;
......@@ -10,7 +10,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { workspaceSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { workspaceSettingsSchemaId, launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
......@@ -201,6 +201,12 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', {
description: nls.localize('workspaceConfig.settings.description', "Workspace settings"),
$ref: workspaceSettingsSchemaId
},
'launch': {
type: 'object',
default: { configurations: [], compounds: [] },
description: nls.localize('workspaceConfig.launch.description', "Workspace launch configurations"),
$ref: launchSchemaId
},
'extensions': {
type: 'object',
default: {},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册