diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index d16fc32ee32..72602a41821 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -735,8 +735,8 @@ export class SelectActionItem extends BaseActionItem { } private registerListeners(): void { - this.toDispose.push(this.selectBox.onDidSelect(selected => { - this.actionRunner.run(this._action, this.getActionContext(selected)).done(); + this.toDispose.push(this.selectBox.onDidSelect(e => { + this.actionRunner.run(this._action, this.getActionContext(e.selected)).done(); })); } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 2df6865710e..04a0d41fdb0 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -26,13 +26,18 @@ export const defaultStyles = { selectBorder: Color.fromHex('#3C3C3C') }; +export interface ISelectData { + selected: string; + index: number; +} + export class SelectBox extends Widget { private selectElement: HTMLSelectElement; private options: string[]; private selected: number; private container: HTMLElement; - private _onDidSelect: Emitter; + private _onDidSelect: Emitter; private toDispose: IDisposable[]; private selectBackground: Color; private selectForeground: Color; @@ -46,7 +51,7 @@ export class SelectBox extends Widget { this.setOptions(options, selected); this.toDispose = []; - this._onDidSelect = new Emitter(); + this._onDidSelect = new Emitter(); this.selectBackground = styles.selectBackground; this.selectForeground = styles.selectForeground; @@ -54,7 +59,10 @@ export class SelectBox extends Widget { this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { this.selectElement.title = e.target.value; - this._onDidSelect.fire(e.target.value); + this._onDidSelect.fire({ + index: e.target.selectedIndex, + selected: e.target.value + }); })); this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) { @@ -64,7 +72,7 @@ export class SelectBox extends Widget { })); } - public get onDidSelect(): Event { + public get onDidSelect(): Event { return this._onDidSelect.event; } diff --git a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts index c6f34eedb79..d74302cfd6e 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts @@ -75,7 +75,7 @@ export class BreakpointWidget extends ZoneWidget { this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService)); selectBox.render(dom.append(container, $('.breakpoint-select-container'))); selectBox.onDidSelect(e => { - this.hitCountContext = e === 'Hit Count'; + this.hitCountContext = e.selected === 'Hit Count'; if (this.hitCountContext) { this.conditionInput = this.inputBox.value; } else { diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 50d19edfefb..1fe29fe3c03 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -15,7 +15,7 @@ import { SelectActionItem, IActionItem } from 'vs/base/browser/ui/actionbar/acti import { EventEmitter } from 'vs/base/common/eventEmitter'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugService } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, ILaunch } from 'vs/workbench/parts/debug/common/debug'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -32,6 +32,7 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { private container: HTMLElement; private start: HTMLElement; private selectBox: SelectBox; + private options: { name: string, launch: ILaunch }[]; private toDispose: lifecycle.IDisposable[]; constructor( @@ -58,17 +59,16 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { this.updateOptions(); } })); - this.toDispose.push(this.selectBox.onDidSelect(configurationName => { - if (configurationName === StartDebugActionItem.ADD_CONFIGURATION) { - this.selectBox.select(this.debugService.getConfigurationManager().getConfigurationNames().indexOf(this.debugService.getViewModel().selectedConfigurationName)); + this.toDispose.push(this.selectBox.onDidSelect(e => { + if (e.selected === StartDebugActionItem.ADD_CONFIGURATION) { + this.selectBox.select(0); this.commandService.executeCommand('debug.addConfiguration').done(undefined, errors.onUnexpectedError); } else { - this.debugService.getViewModel().setSelectedConfigurationName(configurationName); + this.debugService.getConfigurationManager().selectConfiguration(this.options[e.index].launch, this.options[e.index].name); } })); - this.toDispose.push(this.debugService.getViewModel().onDidSelectConfiguration(configurationName => { - const manager = this.debugService.getConfigurationManager(); - this.selectBox.select(manager.getConfigurationNames().indexOf(configurationName)); + this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => { + this.updateOptions(); })); } @@ -149,14 +149,23 @@ export class StartDebugActionItem extends EventEmitter implements IActionItem { } private updateOptions(): void { - const options = this.debugService.getConfigurationManager().getConfigurationNames(); - if (options.length === 0) { - options.push(nls.localize('noConfigurations', "No Configurations")); + let selected = 0; + this.options = []; + this.debugService.getConfigurationManager().getLaunches().forEach(launch => { + launch.getConfigurationNames().forEach(name => { + if (name === this.debugService.getConfigurationManager().selectedName && launch === this.debugService.getConfigurationManager().selectedLaunch) { + selected = this.options.length; + } + this.options.push({ name, launch }); + }); + }); + const selectOptions = this.options.map(o => o.name); + if (this.options.length === 0) { + selectOptions.push(nls.localize('noConfigurations', "No Configurations")); } - const selected = options.indexOf(this.debugService.getViewModel().selectedConfigurationName); - options.push(StartDebugActionItem.SEPARATOR); - options.push(StartDebugActionItem.ADD_CONFIGURATION); - this.selectBox.setOptions(options, selected, options.length - 2); + selectOptions.push(StartDebugActionItem.SEPARATOR); + selectOptions.push(StartDebugActionItem.ADD_CONFIGURATION); + this.selectBox.setOptions(selectOptions, selected, selectOptions.length - 2); } } diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index c4539c6c8b0..53ecce9c344 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -81,12 +81,12 @@ export class ConfigureAction extends AbstractDebugAction { @IMessageService private messageService: IMessageService ) { super(id, label, 'debug-action configure', debugService, keybindingService); - this.toDispose.push(debugService.getViewModel().onDidSelectConfiguration(configurationName => this.updateClass())); + this.toDispose.push(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); this.updateClass(); } public get tooltip(): string { - if (this.debugService.getViewModel().selectedConfigurationName) { + if (this.debugService.getConfigurationManager().selectedName) { return ConfigureAction.LABEL; } @@ -94,7 +94,7 @@ export class ConfigureAction extends AbstractDebugAction { } private updateClass(): void { - this.class = this.debugService.getViewModel().selectedConfigurationName ? 'debug-action configure' : 'debug-action configure notification'; + this.class = this.debugService.getConfigurationManager().selectedName ? 'debug-action configure' : 'debug-action configure notification'; } public run(event?: any): TPromise { @@ -104,7 +104,7 @@ export class ConfigureAction extends AbstractDebugAction { } const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); - return this.debugService.getConfigurationManager().openConfigFile(sideBySide); + return this.debugService.getConfigurationManager().selectedLaunch.openConfigFile(sideBySide); } } @@ -118,7 +118,7 @@ export class StartAction extends AbstractDebugAction { @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(id, label, 'debug-action start', debugService, keybindingService); - this.debugService.getViewModel().onDidSelectConfiguration(() => { + this.debugService.getConfigurationManager().onDidSelectConfiguration(() => { this.updateEnablement(); }); } @@ -134,8 +134,11 @@ export class StartAction extends AbstractDebugAction { // Disabled if the launch drop down shows the launch config that is already running. protected isEnabled(state: State): boolean { const processes = this.debugService.getModel().getProcesses(); - const compound = this.debugService.getConfigurationManager().getCompound(this.debugService.getViewModel().selectedConfigurationName); - return state !== State.Initializing && processes.every(p => p.name !== this.debugService.getViewModel().selectedConfigurationName) && + const selectedName = this.debugService.getConfigurationManager().selectedName; + const launch = this.debugService.getConfigurationManager().selectedLaunch; + const compound = launch && launch.getCompound(selectedName); + + return state !== State.Initializing && processes.every(p => p.name !== selectedName) && (!compound || !compound.configurations || processes.every(p => compound.configurations.indexOf(p.name) === -1)) && (!this.contextService || this.contextService.hasWorkspace() || processes.length === 0); } diff --git a/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts b/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts index c4cf22bb0cd..e6ff5227d89 100644 --- a/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts @@ -10,12 +10,12 @@ import Quickopen = require('vs/workbench/browser/quickopen'); import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen'); import Model = require('vs/base/parts/quickopen/browser/quickOpenModel'); import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IDebugService } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, ILaunch } from 'vs/workbench/parts/debug/common/debug'; import * as errors from 'vs/base/common/errors'; class DebugEntry extends Model.QuickOpenEntry { - constructor(private debugService: IDebugService, private configurationName: string, highlights: Model.IHighlight[] = []) { + constructor(private debugService: IDebugService, private launch: ILaunch, private configurationName: string, highlights: Model.IHighlight[] = []) { super(highlights); } @@ -32,7 +32,7 @@ class DebugEntry extends Model.QuickOpenEntry { return false; } // Run selected debug configuration - this.debugService.getViewModel().setSelectedConfigurationName(this.configurationName); + this.debugService.getConfigurationManager().selectConfiguration(this.launch, this.configurationName); this.debugService.startDebugging().done(undefined, errors.onUnexpectedError); return true; @@ -53,12 +53,15 @@ export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler { } public getResults(input: string): TPromise { - const configurationNames = this.debugService.getConfigurationManager().getConfigurationNames() - .map(config => ({ config: config, highlights: Filters.matchesContiguousSubString(input, config) })) - .filter(({ highlights }) => !!highlights) - .map(({ config, highlights }) => new DebugEntry(this.debugService, config, highlights)); + const configurations: DebugEntry[] = []; - return TPromise.as(new Model.QuickOpenModel(configurationNames)); + for (let launch of this.debugService.getConfigurationManager().getLaunches()) { + launch.getConfigurationNames().map(config => ({ config: config, highlights: Filters.matchesContiguousSubString(input, config) })) + .filter(({ highlights }) => !!highlights) + .forEach(({ config, highlights }) => configurations.push(new DebugEntry(this.debugService, launch, config, highlights))); + } + + return TPromise.as(new Model.QuickOpenModel(configurations)); } public getAutoFocus(input: string): QuickOpen.IAutoFocus { diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 8b2b3e8fdff..093b30398ce 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -269,19 +269,12 @@ export interface IViewModel extends ITreeElement { setSelectedExpression(expression: IExpression); setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint): void; - selectedConfigurationName: string; - setSelectedConfigurationName(name: string): void; - isMultiProcessView(): boolean; onDidFocusProcess: Event; onDidFocusStackFrame: Event; onDidSelectExpression: Event; onDidSelectFunctionBreakpoint: Event; - /** - * Allows to register on change of selected debug configuration. - */ - onDidSelectConfiguration: Event; } export interface IModel extends ITreeElement { @@ -380,6 +373,43 @@ export interface IRawAdapter extends IRawEnvAdapter { } export interface IConfigurationManager { + /** + * Returns true if breakpoints can be set for a given editor model. Depends on mode. + */ + canSetBreakpointsIn(model: EditorIModel): boolean; + + /** + * Returns null for no folder workspace. Otherwise returns a launch object corresponding to the selected debug configuration. + */ + selectedLaunch: ILaunch; + + selectedName: string; + + selectConfiguration(launch: ILaunch, name: string): void; + + getLaunches(): ILaunch[]; + + /** + * Allows to register on change of selected debug configuration. + */ + onDidSelectConfiguration: Event; + + /** + * Returns a "startSessionCommand" contribution for an adapter with the passed type. + * If no type is specified will try to automatically pick an adapter by looking at + * the active editor language and matching it against the "languages" contribution of an adapter. + */ + getStartSessionCommand(type?: string): TPromise<{ command: string, type: string }>; +} + +export interface ILaunch { + + /** + * Resource pointing to the launch.json this object is wrapping. + */ + uri: uri; + + workspaceUri: uri; /** * Returns a configuration with the specified name. @@ -387,6 +417,12 @@ export interface IConfigurationManager { */ getConfiguration(name: string): IConfig; + /** + * Returns a compound with the specified name. + * Returns null if there is no compound with the specified name. + */ + getCompound(name: string): ICompound; + /** * Returns the names of all configurations and compounds. * Ignores configurations which are invalid. @@ -399,30 +435,11 @@ export interface IConfigurationManager { */ resolveConfiguration(config: IConfig): TPromise; - /** - * Returns a compound with the specified name. - * Returns null if there is no compound with the specified name. - */ - getCompound(name: string): ICompound; - - configFileUri: uri; /** * Opens the launch.json file. Creates if it does not exist. */ openConfigFile(sideBySide: boolean, type?: string): TPromise; - - /** - * Returns true if breakpoints can be set for a given editor model. Depends on mode. - */ - canSetBreakpointsIn(model: EditorIModel): boolean; - - /** - * Returns a "startSessionCommand" contribution for an adapter with the passed type. - * If no type is specified will try to automatically pick an adapter by looking at - * the active editor language and matching it against the "languages" contribution of an adapter. - */ - getStartSessionCommand(type?: string): TPromise<{ command: string, type: string }>; } // Debug service interfaces diff --git a/src/vs/workbench/parts/debug/common/debugViewModel.ts b/src/vs/workbench/parts/debug/common/debugViewModel.ts index fa0a2ac2afa..1211dd5d4e4 100644 --- a/src/vs/workbench/parts/debug/common/debugViewModel.ts +++ b/src/vs/workbench/parts/debug/common/debugViewModel.ts @@ -16,16 +16,14 @@ export class ViewModel implements debug.IViewModel { private _onDidFocusStackFrame: Emitter; private _onDidSelectExpression: Emitter; private _onDidSelectFunctionBreakpoint: Emitter; - private _onDidSelectConfigurationName: Emitter; private multiProcessView: boolean; public changedWorkbenchViewState: boolean; - constructor(private _selectedConfigurationName: string) { + constructor() { this._onDidFocusProcess = new Emitter(); this._onDidFocusStackFrame = new Emitter(); this._onDidSelectExpression = new Emitter(); this._onDidSelectFunctionBreakpoint = new Emitter(); - this._onDidSelectConfigurationName = new Emitter(); this.changedWorkbenchViewState = false; this.multiProcessView = false; } @@ -89,15 +87,6 @@ export class ViewModel implements debug.IViewModel { return this._onDidSelectFunctionBreakpoint.event; } - public get selectedConfigurationName(): string { - return this._selectedConfigurationName; - } - - public setSelectedConfigurationName(configurationName: string): void { - this._selectedConfigurationName = configurationName; - this._onDidSelectConfigurationName.fire(configurationName); - } - public isMultiProcessView(): boolean { return this.multiProcessView; } @@ -105,8 +94,4 @@ export class ViewModel implements debug.IViewModel { public setMultiProcessView(isMultiProcessView: boolean): void { this.multiProcessView = isMultiProcessView; } - - public get onDidSelectConfiguration(): Event { - return this._onDidSelectConfigurationName.event; - } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts b/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts index 80af473e78e..378881a76fd 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts @@ -28,12 +28,11 @@ export function registerCommands(): void { handler(accessor: ServicesAccessor, configurationOrName: IConfig | string) { const debugService = accessor.get(IDebugService); if (!configurationOrName) { - configurationOrName = debugService.getViewModel().selectedConfigurationName; + configurationOrName = debugService.getConfigurationManager().selectedName; } if (typeof configurationOrName === 'string') { - debugService.getViewModel().setSelectedConfigurationName(configurationOrName); - debugService.startDebugging(); + debugService.startDebugging(configurationOrName); } else { debugService.createProcess(configurationOrName); } @@ -204,7 +203,7 @@ export function registerCommands(): void { return TPromise.as(null); } - return manager.openConfigFile(false).done(editor => { + return manager.selectedLaunch.openConfigFile(false).done(editor => { if (editor) { const codeEditor = editor.getControl(); if (codeEditor) { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 3bce46d7bb0..bcd80f46839 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +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 { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -14,6 +16,7 @@ import * as paths from 'vs/base/common/paths'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IModel, isCommonCodeEditor } from 'vs/editor/common/editorCommon'; import { IEditor } from 'vs/platform/editor/common/editor'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import * as extensionsRegistry from 'vs/platform/extensions/common/extensionsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -23,7 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IRawAdapter, ICompound, IDebugConfiguration, DEBUG_SCHEME, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug'; +import { IRawAdapter, ICompound, IDebugConfiguration, DEBUG_SCHEME, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch } from 'vs/workbench/parts/debug/common/debug'; import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -201,10 +204,17 @@ const schema: IJSONSchema = { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(schemaId, schema); +const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; +const DEBUG_SELECTED_ROOT = 'debug.selectedroot'; export class ConfigurationManager implements IConfigurationManager { private adapters: Adapter[]; - private breakpointModeIdsSet: Set; + private breakpointModeIdsSet = new Set(); + private launches: Launch[]; + private _selectedName: string; + private _selectedLaunch: ILaunch; + private toDispose: IDisposable[]; + private _onDidSelectConfigurationName = new Emitter(); constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService, @@ -215,11 +225,16 @@ export class ConfigurationManager implements IConfigurationManager { @IQuickOpenService private quickOpenService: IQuickOpenService, @IConfigurationResolverService private configurationResolverService: IConfigurationResolverService, @IInstantiationService private instantiationService: IInstantiationService, - @ICommandService private commandService: ICommandService + @ICommandService private commandService: ICommandService, + @IStorageService private storageService: IStorageService ) { this.adapters = []; + this.launches = []; + this.toDispose = []; this.registerListeners(); - this.breakpointModeIdsSet = new Set(); + this.initLaunches(); + // TODO@isidor select the appropriate Launch + this.selectConfiguration(this.launches.length ? this.launches[0] : undefined, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE, null)); } private registerListeners(): void { @@ -265,139 +280,65 @@ export class ConfigurationManager implements IConfigurationManager { }); }); }); + + this.toDispose.push(this.contextService.onDidChangeWorkspaceRoots(() => { + })); + + this.toDispose.push(this.configurationService.onDidUpdateConfiguration((event) => { + if (event.sourceConfig) { + // TODO@Isidor react on user changing the launch.json + } + })); + } + + private initLaunches(): void { + this.launches = this.contextService.getWorkspace().roots.map(root => this.instantiationService.createInstance(Launch, this, root)); + } + + public getLaunches(): ILaunch[] { + return this.launches; + } + + public get selectedLaunch(): ILaunch { + return this._selectedLaunch; + } + + public get selectedName(): string { + return this._selectedName; + } + + public get onDidSelectConfiguration(): Event { + return this._onDidSelectConfigurationName.event; + } + + public selectConfiguration(launch: ILaunch, name: string): void { + this._selectedLaunch = launch; + this._selectedName = name; + this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE); + if (launch) { + this.storageService.store(DEBUG_SELECTED_ROOT, this.contextService.getRoot(launch.uri).toString(), StorageScope.WORKSPACE); + } + this._onDidSelectConfigurationName.fire(); + } + + public canSetBreakpointsIn(model: IModel): boolean { + if (model.uri.scheme !== Schemas.file && model.uri.scheme !== DEBUG_SCHEME) { + return false; + } + if (this.configurationService.getConfiguration('debug').allowBreakpointsEverywhere) { + return true; + } + + const modeId = model ? model.getLanguageIdentifier().language : null; + + return this.breakpointModeIdsSet.has(modeId); } public getAdapter(type: string): Adapter { return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, type)).pop(); } - public getCompound(name: string): ICompound { - if (!this.contextService.hasWorkspace()) { - return null; - } - - const config = this.configurationService.getConfiguration('launch', { resource: this.contextService.getLegacyWorkspace().resource }); // TODO@Isidor (https://github.com/Microsoft/vscode/issues/29245) - if (!config || !config.compounds) { - return null; - } - - return config.compounds.filter(compound => compound.name === name).pop(); - } - - public getConfigurationNames(): string[] { - const config = this.contextService.hasWorkspace() ? this.configurationService.getConfiguration('launch', { resource: this.contextService.getLegacyWorkspace().resource }) : null; // TODO@Isidor (https://github.com/Microsoft/vscode/issues/29245) - if (!config || !config.configurations) { - return []; - } else { - const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name); - if (names.length > 0 && config.compounds) { - if (config.compounds) { - names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length) - .map(compound => compound.name)); - } - } - - return names; - } - } - - public getConfiguration(name: string): IConfig { - if (!this.contextService.hasWorkspace()) { - return null; - } - - const config = this.configurationService.getConfiguration('launch', { resource: this.contextService.getLegacyWorkspace().resource }); // TODO@Isidor (https://github.com/Microsoft/vscode/issues/29245) - if (!config || !config.configurations) { - return null; - } - - return config.configurations.filter(config => config && config.name === name).shift(); - } - - public resolveConfiguration(config: IConfig): TPromise { - if (!this.contextService.hasWorkspace()) { - return TPromise.as(config); - } - - const result = objects.deepClone(config) as IConfig; - // Set operating system specific properties #1873 - const setOSProperties = (flag: boolean, osConfig: IEnvConfig) => { - if (flag && osConfig) { - Object.keys(osConfig).forEach(key => { - result[key] = osConfig[key]; - }); - } - }; - setOSProperties(isWindows, result.windows); - setOSProperties(isMacintosh, result.osx); - setOSProperties(isLinux, result.linux); - - // massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths. - Object.keys(result).forEach(key => { - result[key] = this.configurationResolverService.resolveAny(result[key]); - }); - - const adapter = this.getAdapter(result.type); - return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null); - } - - public get configFileUri(): uri { - return uri.file(paths.join(this.contextService.getLegacyWorkspace().resource.fsPath, '/.vscode/launch.json')); // TODO@Isidor (https://github.com/Microsoft/vscode/issues/29245) - } - - public openConfigFile(sideBySide: boolean, type?: string): TPromise { - const resource = this.configFileUri; - let configFileCreated = false; - - // temporary workaround: the folderUri should be an argument to openConfigFile - let folderUri: uri = undefined; - const workspace = this.contextService.getWorkspace(); - if (workspace && workspace.roots.length > 0) { - folderUri = workspace.roots[0]; - } - - return this.fileService.resolveContent(resource).then(content => true, err => - this.guessAdapter(type).then(adapter => adapter ? adapter.getInitialConfigurationContent(folderUri) : undefined) - .then(content => { - if (!content) { - return false; - } - - configFileCreated = true; - return this.fileService.updateContent(resource, content).then(() => true); - })) - .then(errorFree => { - if (!errorFree) { - return undefined; - } - this.telemetryService.publicLog('debugConfigure'); - - return this.editorService.openEditor({ - resource: resource, - options: { - forceOpen: true, - pinned: configFileCreated, // pin only if config file is created #8727 - revealIfVisible: true - }, - }, sideBySide); - }, (error) => { - throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error)); - }); - } - - public getStartSessionCommand(type?: string): TPromise<{ command: string, type: string }> { - return this.guessAdapter(type).then(adapter => { - if (adapter) { - return { - command: adapter.startSessionCommand, - type: adapter.type - }; - } - return undefined; - }); - } - - private guessAdapter(type?: string): TPromise { + public guessAdapter(type?: string): TPromise { if (type) { const adapter = this.getAdapter(type); return TPromise.as(adapter); @@ -428,16 +369,126 @@ export class ConfigurationManager implements IConfigurationManager { }); } - public canSetBreakpointsIn(model: IModel): boolean { - if (model.uri.scheme !== Schemas.file && model.uri.scheme !== DEBUG_SCHEME) { - return false; - } - if (this.configurationService.getConfiguration('debug').allowBreakpointsEverywhere) { - return true; - } + public getStartSessionCommand(type?: string): TPromise<{ command: string, type: string }> { + return this.guessAdapter(type).then(adapter => { + if (adapter) { + return { + command: adapter.startSessionCommand, + type: adapter.type + }; + } + return undefined; + }); + } - const modeId = model ? model.getLanguageIdentifier().language : null; - - return this.breakpointModeIdsSet.has(modeId); + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } +} + +class Launch implements ILaunch { + + constructor( + private configurationManager: ConfigurationManager, + public workspaceUri: uri, + @IFileService private fileService: IFileService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IConfigurationService private configurationService: IConfigurationService, + @IConfigurationResolverService private configurationResolverService: IConfigurationResolverService, + ) { + } + + public getCompound(name: string): ICompound { + const config = this.configurationService.getConfiguration('launch', { resource: this.workspaceUri }); + if (!config || !config.compounds) { + return null; + } + + return config.compounds.filter(compound => compound.name === name).pop(); + } + + public getConfigurationNames(): string[] { + const config = this.configurationService.getConfiguration('launch', { resource: this.workspaceUri }); + if (!config || !config.configurations) { + return []; + } else { + const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name); + if (names.length > 0 && config.compounds) { + if (config.compounds) { + names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length) + .map(compound => compound.name)); + } + } + + return names; + } + } + + public getConfiguration(name: string): IConfig { + const config = this.configurationService.getConfiguration('launch', { resource: this.workspaceUri }); + if (!config || !config.configurations) { + return null; + } + + return config.configurations.filter(config => config && config.name === name).shift(); + } + + public resolveConfiguration(config: IConfig): TPromise { + const result = objects.deepClone(config) as IConfig; + // Set operating system specific properties #1873 + const setOSProperties = (flag: boolean, osConfig: IEnvConfig) => { + if (flag && osConfig) { + Object.keys(osConfig).forEach(key => { + result[key] = osConfig[key]; + }); + } + }; + setOSProperties(isWindows, result.windows); + setOSProperties(isMacintosh, result.osx); + setOSProperties(isLinux, result.linux); + + // massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths. + Object.keys(result).forEach(key => { + result[key] = this.configurationResolverService.resolveAny(result[key]); + }); + + const adapter = this.configurationManager.getAdapter(result.type); + return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null); + } + + public get uri(): uri { + return uri.file(paths.join(this.workspaceUri.fsPath, '/.vscode/launch.json')); + } + + public openConfigFile(sideBySide: boolean, type?: string): TPromise { + const resource = this.uri; + let configFileCreated = false; + + return this.fileService.resolveContent(resource).then(content => true, err => + this.configurationManager.guessAdapter(type).then(adapter => adapter ? adapter.getInitialConfigurationContent(this.workspaceUri) : undefined) + .then(content => { + if (!content) { + return false; + } + + configFileCreated = true; + return this.fileService.updateContent(resource, content).then(() => true); + })) + .then(errorFree => { + if (!errorFree) { + return undefined; + } + + return this.editorService.openEditor({ + resource: resource, + options: { + forceOpen: true, + pinned: configFileCreated, // pin only if config file is created #8727 + revealIfVisible: true + }, + }, sideBySide); + }, (error) => { + throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error)); + }); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 640e9b4aebe..d005eeaac70 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -56,7 +56,6 @@ const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; const DEBUG_EXCEPTION_BREAKPOINTS_KEY = 'debug.exceptionbreakpoint'; const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions'; -const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; interface StartSessionResult { status: 'ok' | 'initialConfiguration' | 'saveConfiguration'; @@ -120,6 +119,7 @@ export class DebugService implements debug.IDebugService { this.allSessionIds = new Set(); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager); + this.toDispose.push(this.configurationManager); this.inDebugMode = debug.CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); this.debugType = debug.CONTEXT_DEBUG_TYPE.bindTo(contextKeyService); this.isNodeDebugType = debug.CONTEXT_IS_NODE_DEBUG_TYPE.bindTo(contextKeyService); @@ -128,7 +128,7 @@ export class DebugService implements debug.IDebugService { this.model = new Model(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(), this.loadExceptionBreakpoints(), this.loadWatchExpressions()); this.toDispose.push(this.model); - this.viewModel = new ViewModel(this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE, null)); + this.viewModel = new ViewModel(); this.registerListeners(lifecycleService); } @@ -154,15 +154,6 @@ export class DebugService implements debug.IDebugService { lifecycleService.onShutdown(this.dispose, this); this.toDispose.push(this.broadcastService.onBroadcast(this.onBroadcast, this)); - this.toDispose.push(this.configurationService.onDidUpdateConfiguration((event) => { - if (event.sourceConfig) { - const names = this.configurationManager.getConfigurationNames(); - if (names.every(name => name !== this.viewModel.selectedConfigurationName)) { - // Current selected configuration no longer exists - take the first configuration instead. - this.viewModel.setSelectedConfigurationName(names.length ? names[0] : undefined); - } - } - })); } private onBroadcast(broadcast: IBroadcast): void { @@ -180,8 +171,8 @@ export class DebugService implements debug.IDebugService { this.onSessionEnd(session); } - const config = this.configurationManager.getConfiguration(this.viewModel.selectedConfigurationName); - this.configurationManager.resolveConfiguration(config).done(resolvedConfig => { + const config = this.configurationManager.selectedLaunch.getConfiguration(this.configurationManager.selectedName); + this.configurationManager.selectedLaunch.resolveConfiguration(config).done(resolvedConfig => { resolvedConfig.request = 'attach'; resolvedConfig.port = broadcast.payload.port; this.doCreateProcess(resolvedConfig, broadcast.payload.debugId); @@ -665,12 +656,12 @@ export class DebugService implements debug.IDebugService { let config: debug.IConfig, compound: debug.ICompound; if (!configOrName) { - configOrName = this.viewModel.selectedConfigurationName; + configOrName = this.configurationManager.selectedName; } - if (typeof configOrName === 'string') { - config = manager.getConfiguration(configOrName); - compound = manager.getCompound(configOrName); - } else { + if (typeof configOrName === 'string' && manager.selectedLaunch) { + config = manager.selectedLaunch.getConfiguration(configOrName); + compound = manager.selectedLaunch.getCompound(configOrName); + } else if (typeof configOrName !== 'string') { config = configOrName; } @@ -690,16 +681,17 @@ export class DebugService implements debug.IDebugService { if (noDebug && config) { config.noDebug = true; } + const selectedLaunch = manager.selectedLaunch; if (commandAndType && commandAndType.command) { const defaultConfig = noDebug ? { noDebug: true } : {}; - return this.commandService.executeCommand(commandAndType.command, config || defaultConfig, folderUri).then((result: StartSessionResult) => { - if (this.contextService.hasWorkspace()) { + return this.commandService.executeCommand(commandAndType.command, config || defaultConfig, selectedLaunch.workspaceUri).then((result: StartSessionResult) => { + if (selectedLaunch) { if (result && result.status === 'initialConfiguration') { - return manager.openConfigFile(false, commandAndType.type); + return selectedLaunch.openConfigFile(false, commandAndType.type); } if (result && result.status === 'saveConfiguration') { - return this.fileService.updateContent(manager.configFileUri, result.content).then(() => manager.openConfigFile(false)); + return this.fileService.updateContent(selectedLaunch.uri, result.content).then(() => selectedLaunch.openConfigFile(false)); } } return undefined; @@ -709,8 +701,8 @@ export class DebugService implements debug.IDebugService { if (config) { return this.createProcess(config); } - if (this.contextService.hasWorkspace() && commandAndType) { - return manager.openConfigFile(false, commandAndType.type); + if (selectedLaunch && commandAndType) { + return selectedLaunch.openConfigFile(false, commandAndType.type); } return undefined; @@ -730,7 +722,7 @@ export class DebugService implements debug.IDebugService { public createProcess(config: debug.IConfig): TPromise { return this.textFileService.saveAll().then(() => - this.configurationManager.resolveConfiguration(config).then(resolvedConfig => { + (this.configurationManager.selectedLaunch ? this.configurationManager.selectedLaunch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => { if (!resolvedConfig) { // User canceled resolving of interactive variables, silently return return undefined; @@ -779,7 +771,7 @@ export class DebugService implements debug.IDebugService { return this.messageService.show(severity.Error, nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved on disk and that you have a debug extension installed for that file type.")); } - return this.configurationManager.openConfigFile(false).then(openend => { + return this.configurationManager.selectedLaunch.openConfigFile(false).then(openend => { if (openend) { this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application. {0}", err.message)); } @@ -961,9 +953,9 @@ export class DebugService implements debug.IDebugService { setTimeout(() => { // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration let config = process.configuration; - if (this.launchJsonChanged) { + if (this.launchJsonChanged && this.configurationManager.selectedLaunch) { this.launchJsonChanged = false; - config = this.configurationManager.getConfiguration(process.configuration.name) || config; + config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config; // Take the type from the process since the debug extension might overwrite it #21316 config.type = process.configuration.type; config.noDebug = process.configuration.noDebug; @@ -1193,8 +1185,6 @@ export class DebugService implements debug.IDebugService { this.storageService.remove(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); } - this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.viewModel.selectedConfigurationName, StorageScope.WORKSPACE); - const watchExpressions = this.model.getWatchExpressions(); if (watchExpressions.length) { this.storageService.store(DEBUG_WATCH_EXPRESSIONS_KEY, JSON.stringify(watchExpressions.map(we => ({ name: we.name, id: we.getId() }))), StorageScope.WORKSPACE); diff --git a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts b/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts index 6e6596e17aa..4904ff06f3e 100644 --- a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts +++ b/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts @@ -12,7 +12,7 @@ suite('Debug - View Model', () => { let model: ViewModel; setup(() => { - model = new ViewModel('mockconfiguration'); + model = new ViewModel(); }); teardown(() => { @@ -47,13 +47,4 @@ suite('Debug - View Model', () => { model.setMultiProcessView(true); assert.equal(model.isMultiProcessView(), true); }); - - test('selected configuration name', () => { - model.onDidSelectConfiguration(name => { - assert.equal(name, 'configName'); - }); - assert.equal(model.selectedConfigurationName, 'mockconfiguration'); - model.setSelectedConfigurationName('configName'); - assert.equal(model.selectedConfigurationName, 'configName'); - }); }); diff --git a/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts b/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts index 105a0aa9fb8..420f398feaa 100644 --- a/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts +++ b/src/vs/workbench/parts/debug/test/node/debugAdapter.test.ts @@ -110,7 +110,7 @@ suite('Debug - Adapter', () => { }); test('initial config file content', () => { - adapter.getInitialConfigurationContent().then(content => { + adapter.getInitialConfigurationContent(null).then(content => { const expected = ['{', ' "version": "0.2.0",', ' "configurations": [',