/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as objects from 'vs/base/common/objects'; import * as json from 'vs/base/common/json'; import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IEditorPane } from 'vs/workbench/common/editor'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDebugConfigurationProvider, ICompound, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, CONTEXT_DEBUG_CONFIGURATION_TYPE, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { launchSchema } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { sequence } from 'vs/base/common/async'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { flatten, distinct } from 'vs/base/common/arrays'; import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; const DEBUG_SELECTED_ROOT = 'debug.selectedroot'; // Debug type is only stored if a dynamic configuration is used for better restore const DEBUG_SELECTED_TYPE = 'debug.selectedtype'; const DEBUG_RECENT_DYNAMIC_CONFIGURATIONS = 'debug.recentdynamicconfigurations'; interface IDynamicPickItem { label: string, launch: ILaunch, config: IConfig } export class ConfigurationManager implements IConfigurationManager { private launches!: ILaunch[]; private selectedName: string | undefined; private selectedLaunch: ILaunch | undefined; private getSelectedConfig: () => Promise = () => Promise.resolve(undefined); private selectedType: string | undefined; private toDispose: IDisposable[]; private readonly _onDidSelectConfigurationName = new Emitter(); private configProviders: IDebugConfigurationProvider[]; private debugConfigurationTypeContext: IContextKey; constructor( private readonly adapterManager: AdapterManager, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IHistoryService private readonly historyService: IHistoryService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IContextKeyService contextKeyService: IContextKeyService ) { this.configProviders = []; this.toDispose = []; this.initLaunches(); this.registerListeners(); const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedType = this.storageService.get(DEBUG_SELECTED_TYPE, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.find(l => l.uri.toString() === previousSelectedRoot); const previousSelectedName = this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, { type: previousSelectedType }); } else if (this.launches.length > 0) { this.selectConfiguration(undefined, previousSelectedName, undefined, { type: previousSelectedType }); } } registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { this.configProviders.push(debugConfigurationProvider); return { dispose: () => { this.unregisterDebugConfigurationProvider(debugConfigurationProvider); } }; } unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void { const ix = this.configProviders.indexOf(debugConfigurationProvider); if (ix >= 0) { this.configProviders.splice(ix, 1); } } /** * if scope is not specified,a value of DebugConfigurationProvideTrigger.Initial is assumed. */ hasDebugConfigurationProvider(debugType: string, triggerKind?: DebugConfigurationProviderTriggerKind): boolean { if (triggerKind === undefined) { triggerKind = DebugConfigurationProviderTriggerKind.Initial; } // check if there are providers for the given type that contribute a provideDebugConfigurations method const provider = this.configProviders.find(p => p.provideDebugConfigurations && (p.type === debugType) && (p.triggerKind === triggerKind)); return !!provider; } async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise { await this.activateDebuggers('onDebugResolve', type); // pipe the config through the promises sequentially. Append at the end the '*' types const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); let result: IConfig | null | undefined = config; await sequence(providers.map(provider => async () => { // If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver if (result) { result = await provider.resolveDebugConfiguration!(folderUri, result, token); } })); return result; } async resolveDebugConfigurationWithSubstitutedVariables(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise { // pipe the config through the promises sequentially. Append at the end the '*' types const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfigurationWithSubstitutedVariables) .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfigurationWithSubstitutedVariables)); let result: IConfig | null | undefined = config; await sequence(providers.map(provider => async () => { // If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver if (result) { result = await provider.resolveDebugConfigurationWithSubstitutedVariables!(folderUri, result, token); } })); return result; } async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { await this.activateDebuggers('onDebugInitialConfigurations'); const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Initial && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))); return results.reduce((first, second) => first.concat(second), []); } async getDynamicProviders(): Promise<{ label: string, type: string, getProvider: () => Promise, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> { const extensions = await this.extensionService.getExtensions(); const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations'; const debugDynamicExtensionsTypes = extensions.reduce((acc, e) => { if (!e.activationEvents) { return acc; } const explicitTypes: string[] = []; let hasGenericEvent = false; for (const event of e.activationEvents) { if (event === onDebugDynamicConfigurationsName) { hasGenericEvent = true; } else if (event.startsWith(`${onDebugDynamicConfigurationsName}:`)) { explicitTypes.push(event.slice(onDebugDynamicConfigurationsName.length + 1)); } } if (explicitTypes.length) { return acc.concat(explicitTypes); } if (hasGenericEvent) { const debuggerType = e.contributes?.debuggers?.[0].type; return debuggerType ? acc.concat(debuggerType) : acc; } return acc; }, [] as string[]); return debugDynamicExtensionsTypes.map(type => { return { label: this.adapterManager.getDebuggerLabel(type)!, getProvider: async () => { await this.activateDebuggers(onDebugDynamicConfigurationsName, type); return this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations); }, type, pick: async () => { // Do a late 'onDebugDynamicConfigurationsName' activation so extensions are not activated too early #108578 await this.activateDebuggers(onDebugDynamicConfigurationsName, type); const disposables = new DisposableStore(); const input = disposables.add(this.quickInputService.createQuickPick()); input.busy = true; input.placeholder = nls.localize('selectConfiguration', "Select Launch Configuration"); input.show(); const chosenPromise = new Promise(resolve => { disposables.add(input.onDidAccept(() => resolve(input.activeItems[0]))); disposables.add(input.onDidTriggerItemButton(async (context) => { resolve(undefined); const { launch, config } = context.item; await launch.openConfigFile(false, config.type); // Only Launch have a pin trigger button await (launch as Launch).writeConfiguration(config); await this.selectConfiguration(launch, config.name); })); }); const token = new CancellationTokenSource(); const picks: Promise[] = []; const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations); this.getLaunches().forEach(launch => { if (launch.workspace && provider) { picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({ label: config.name, description: launch.name, config, buttons: [{ iconClass: 'codicon-gear', tooltip: nls.localize('editLaunchConfig', "Edit Debug Configuration in launch.json") }], launch })))); } }); const nestedPicks = await Promise.all(picks); const items = flatten(nestedPicks); input.items = items; input.busy = false; const chosen = await chosenPromise; disposables.dispose(); if (!chosen) { // User canceled quick input we should notify the provider to cancel computing configurations token.cancel(); return; } return chosen; } }; }); } getAllConfigurations(): { launch: ILaunch; name: string; presentation?: IConfigPresentation }[] { const all: { launch: ILaunch, name: string, presentation?: IConfigPresentation }[] = []; for (let l of this.launches) { for (let name of l.getConfigurationNames()) { const config = l.getConfiguration(name) || l.getCompound(name); if (config) { all.push({ launch: l, name, presentation: config.presentation }); } } } return getVisibleAndSorted(all); } getRecentDynamicConfigurations(): { name: string, type: string }[] { return JSON.parse(this.storageService.get(DEBUG_RECENT_DYNAMIC_CONFIGURATIONS, StorageScope.WORKSPACE, '[]')); } private registerListeners(): void { this.toDispose.push(Event.any(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => { this.initLaunches(); this.selectConfiguration(undefined); this.setCompoundSchemaValues(); })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('launch')) { // A change happen in the launch.json. If there is already a launch configuration selected, do not change the selection. this.selectConfiguration(undefined); this.setCompoundSchemaValues(); } })); this.toDispose.push(this.adapterManager.onDidDebuggersExtPointRead(() => { this.setCompoundSchemaValues(); })); } private initLaunches(): void { this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, this.adapterManager, folder)); if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this, this.adapterManager)); } this.launches.push(this.instantiationService.createInstance(UserLaunch, this, this.adapterManager)); if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) { this.selectConfiguration(undefined); } } private setCompoundSchemaValues(): void { const compoundConfigurationsSchema = (launchSchema.properties!['compounds'].items).properties!['configurations']; const launchNames = this.launches.map(l => l.getConfigurationNames(true)).reduce((first, second) => first.concat(second), []); (compoundConfigurationsSchema.items).oneOf![0].enum = launchNames; (compoundConfigurationsSchema.items).oneOf![1].properties!.name.enum = launchNames; const folderNames = this.contextService.getWorkspace().folders.map(f => f.name); (compoundConfigurationsSchema.items).oneOf![1].properties!.folder.enum = folderNames; jsonRegistry.registerSchema(launchSchemaId, launchSchema); } getLaunches(): ILaunch[] { return this.launches; } getLaunch(workspaceUri: uri | undefined): ILaunch | undefined { if (!uri.isUri(workspaceUri)) { return undefined; } return this.launches.find(l => l.workspace && this.uriIdentityService.extUri.isEqual(l.workspace.uri, workspaceUri)); } get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined, getConfig: () => Promise, type: string | undefined } { return { launch: this.selectedLaunch, name: this.selectedName, getConfig: this.getSelectedConfig, type: this.selectedType }; } get onDidSelectConfiguration(): Event { return this._onDidSelectConfigurationName.event; } getWorkspaceLaunch(): ILaunch | undefined { if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { return this.launches[this.launches.length - 1]; } return undefined; } async selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, dynamicConfig?: { type?: string }): Promise { if (typeof launch === 'undefined') { const rootUri = this.historyService.getLastActiveWorkspaceRoot(); launch = this.getLaunch(rootUri); if (!launch || launch.getConfigurationNames().length === 0) { launch = this.launches.find(l => !!(l && l.getConfigurationNames().length)) || launch || this.launches[0]; } } const previousLaunch = this.selectedLaunch; const previousName = this.selectedName; this.selectedLaunch = launch; if (this.selectedLaunch) { this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); } const names = launch ? launch.getConfigurationNames() : []; this.getSelectedConfig = () => Promise.resolve(config); let type = config?.type; if (name && names.indexOf(name) >= 0) { this.setSelectedLaunchName(name); } else if (dynamicConfig && dynamicConfig.type) { // We could not find the previously used name and config is not passed. We should get all dynamic configurations from providers // And potentially auto select the previously used dynamic configuration #96293 type = dynamicConfig.type; if (!config) { const providers = (await this.getDynamicProviders()).filter(p => p.type === type); this.getSelectedConfig = async () => { const activatedProviders = await Promise.all(providers.map(p => p.getProvider())); const provider = activatedProviders.find(p => p && p.type === type); if (provider && launch && launch.workspace) { const token = new CancellationTokenSource(); const dynamicConfigs = await provider.provideDebugConfigurations!(launch.workspace.uri, token.token); const dynamicConfig = dynamicConfigs.find(c => c.name === name); if (dynamicConfig) { return dynamicConfig; } } return undefined; }; } this.setSelectedLaunchName(name); let recentDynamicProviders = this.getRecentDynamicConfigurations(); if (name && dynamicConfig.type) { // We need to store the recently used dynamic configurations to be able to show them in UI #110009 recentDynamicProviders.unshift({ name, type: dynamicConfig.type }); recentDynamicProviders = distinct(recentDynamicProviders, t => `${t.name} : ${t.type}`); this.storageService.store(DEBUG_RECENT_DYNAMIC_CONFIGURATIONS, JSON.stringify(recentDynamicProviders), StorageScope.WORKSPACE, StorageTarget.USER); } } else if (!this.selectedName || names.indexOf(this.selectedName) === -1) { // We could not find the configuration to select, pick the first one, or reset the selection if there is no launch configuration const nameToSet = names.length ? names[0] : undefined; this.setSelectedLaunchName(nameToSet); } this.selectedType = dynamicConfig?.type || config?.type; this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedType, StorageScope.WORKSPACE, StorageTarget.MACHINE); if (type) { this.debugConfigurationTypeContext.set(type); } else { this.debugConfigurationTypeContext.reset(); } if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) { this._onDidSelectConfigurationName.fire(); } } async activateDebuggers(activationEvent: string, debugType?: string): Promise { const promises: Promise[] = [ this.extensionService.activateByEvent(activationEvent), this.extensionService.activateByEvent('onDebug') ]; if (debugType) { promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`)); } await Promise.all(promises); } private setSelectedLaunchName(selectedName: string | undefined): void { this.selectedName = selectedName; if (this.selectedName) { this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE, StorageTarget.MACHINE); } else { this.storageService.remove(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE); } } dispose(): void { this.toDispose = dispose(this.toDispose); } } abstract class AbstractLaunch { protected abstract getConfig(): IGlobalConfig | undefined; constructor( protected configurationManager: ConfigurationManager, private readonly adapterManager: AdapterManager ) { } getCompound(name: string): ICompound | undefined { const config = this.getConfig(); if (!config || !config.compounds) { return undefined; } return config.compounds.find(compound => compound.name === name); } getConfigurationNames(ignoreCompoundsAndPresentation = false): string[] { const config = this.getConfig(); if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) { return []; } else { const configurations: (IConfig | ICompound)[] = []; if (config.configurations) { configurations.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string')); } if (ignoreCompoundsAndPresentation) { return configurations.map(c => c.name); } if (config.compounds) { configurations.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)); } return getVisibleAndSorted(configurations).map(c => c.name); } } getConfiguration(name: string): IConfig | undefined { // We need to clone the configuration in order to be able to make changes to it #42198 const config = objects.deepClone(this.getConfig()); if (!config || !config.configurations) { return undefined; } const configuration = config.configurations.find(config => config && config.name === name); if (configuration) { if (this instanceof UserLaunch) { configuration.__configurationTarget = ConfigurationTarget.USER; } else if (this instanceof WorkspaceLaunch) { configuration.__configurationTarget = ConfigurationTarget.WORKSPACE; } else { configuration.__configurationTarget = ConfigurationTarget.WORKSPACE_FOLDER; } } return configuration; } async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise { let content = ''; const adapter = await this.adapterManager.guessDebugger(type); if (adapter) { const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None); content = await adapter.getInitialConfigurationContent(initialConfigs); } return content; } get hidden(): boolean { return false; } } class Launch extends AbstractLaunch implements ILaunch { constructor( configurationManager: ConfigurationManager, adapterManager: AdapterManager, public workspace: IWorkspaceFolder, @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(configurationManager, adapterManager); } get uri(): uri { return resources.joinPath(this.workspace.uri, '/.vscode/launch.json'); } get name(): string { return this.workspace.name; } protected getConfig(): IGlobalConfig | undefined { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolderValue; } async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { const resource = this.uri; let created = false; let content = ''; try { const fileContent = await this.fileService.readFile(resource); content = fileContent.value.toString(); } catch { // launch.json not found: create one by collecting launch configs from debugConfigProviders content = await this.getInitialConfigurationContent(this.workspace.uri, type, token); if (content) { created = true; // pin only if config file is created #8727 try { await this.textFileService.write(resource, content); } catch (error) { throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); } } } if (content === '') { return { editor: null, created: false }; } const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); let startLineNumber = 1; for (let i = 0; i < index; i++) { if (content.charAt(i) === '\n') { startLineNumber++; } } const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; const editor = await this.editorService.openEditor({ resource, options: { selection, preserveFocus, pinned: created, revealIfVisible: true }, }, ACTIVE_GROUP); return ({ editor: withUndefinedAsNull(editor), created }); } async writeConfiguration(configuration: IConfig): Promise { const fullConfig = objects.deepClone(this.getConfig()!); if (!fullConfig.configurations) { fullConfig.configurations = []; } fullConfig.configurations.push(configuration); await this.configurationService.updateValue('launch', fullConfig, { resource: this.workspace.uri }, ConfigurationTarget.WORKSPACE_FOLDER); } } class WorkspaceLaunch extends AbstractLaunch implements ILaunch { constructor( configurationManager: ConfigurationManager, adapterManager: AdapterManager, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { super(configurationManager, adapterManager); } get workspace(): undefined { return undefined; } get uri(): uri { return this.contextService.getWorkspace().configuration!; } get name(): string { return nls.localize('workspace', "workspace"); } protected getConfig(): IGlobalConfig | undefined { return this.configurationService.inspect('launch').workspaceValue; } async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { let launchExistInFile = !!this.getConfig(); if (!launchExistInFile) { // Launch property in workspace config not found: create one by collecting launch configs from debugConfigProviders let content = await this.getInitialConfigurationContent(undefined, type, token); if (content) { await this.configurationService.updateValue('launch', json.parse(content), ConfigurationTarget.WORKSPACE); } else { return { editor: null, created: false }; } } const editor = await this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, options: { preserveFocus } }, ACTIVE_GROUP); return ({ editor: withUndefinedAsNull(editor), created: false }); } } class UserLaunch extends AbstractLaunch implements ILaunch { constructor( configurationManager: ConfigurationManager, adapterManager: AdapterManager, @IConfigurationService private readonly configurationService: IConfigurationService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { super(configurationManager, adapterManager); } get workspace(): undefined { return undefined; } get uri(): uri { return this.preferencesService.userSettingsResource; } get name(): string { return nls.localize('user settings', "user settings"); } get hidden(): boolean { return true; } protected getConfig(): IGlobalConfig | undefined { return this.configurationService.inspect('launch').userValue; } async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus }); return ({ editor: withUndefinedAsNull(editor), created: false }); } }