Files
vscode/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts
T

732 lines
28 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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 } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import * as strings from 'vs/base/common/strings';
import * as objects from 'vs/base/common/objects';
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 { ITextModel } from 'vs/editor/common/model';
import { IEditorPane } from 'vs/workbench/common/editor';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IConfigurationService } 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 { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
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, debuggersExtPoint, breakpointsExtPoint } 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 } 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 { first } from 'vs/base/common/arrays';
import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils';
import { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
export class ConfigurationManager implements IConfigurationManager {
private debuggers: Debugger[];
private breakpointModeIdsSet = new Set<string>();
private launches!: ILaunch[];
private selectedName: string | undefined;
private selectedLaunch: ILaunch | undefined;
private toDispose: IDisposable[];
private readonly _onDidSelectConfigurationName = new Emitter<void>();
private configProviders: IDebugConfigurationProvider[];
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
private debugAdapterFactories = new Map<string, IDebugAdapterFactory>();
private debugConfigurationTypeContext: IContextKey<string>;
private readonly _onDidRegisterDebugger = new Emitter<void>();
constructor(
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICommandService private readonly commandService: ICommandService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,
@IHistoryService private readonly historyService: IHistoryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.configProviders = [];
this.adapterDescriptorFactories = [];
this.debuggers = [];
this.toDispose = [];
this.initLaunches();
this.registerListeners();
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop();
this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService);
if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) {
this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
} else if (this.launches.length > 0) {
this.selectConfiguration(undefined);
}
}
// debuggers
registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable {
debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher));
return {
dispose: () => {
debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType));
}
};
}
createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined {
let dap = this.debugAdapterFactories.get(session.configuration.type);
if (dap) {
return dap.createDebugAdapter(session);
}
return undefined;
}
substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
let dap = this.debugAdapterFactories.get(debugType);
if (dap) {
return dap.substituteVariables(folder, config);
}
return Promise.resolve(config);
}
runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
let tl = this.debugAdapterFactories.get(debugType);
if (tl) {
return tl.runInTerminal(args);
}
return Promise.resolve(void 0);
}
// debug adapter
registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable {
this.adapterDescriptorFactories.push(debugAdapterProvider);
return {
dispose: () => {
this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider);
}
};
}
unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void {
const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider);
if (ix >= 0) {
this.adapterDescriptorFactories.splice(ix, 1);
}
}
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined> {
const config = session.configuration;
// first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable
const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable);
if (providers0.length === 1 && providers0[0].debugAdapterExecutable) {
return providers0[0].debugAdapterExecutable(session.root ? session.root.uri : undefined);
} else {
// TODO@AW handle n > 1 case
}
// new API
const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor);
if (providers.length === 1) {
return providers[0].createDebugAdapterDescriptor(session);
} else {
// TODO@AW handle n > 1 case
}
return Promise.resolve(undefined);
}
getDebuggerLabel(session: IDebugSession): string | undefined {
const dbgr = this.getDebugger(session.configuration.type);
if (dbgr) {
return dbgr.label;
}
return undefined;
}
get onDidRegisterDebugger(): Event<void> {
return this._onDidRegisterDebugger.event;
}
// debug configurations
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 DebugConfigurationProviderScope.Initialization is assumed.
*/
hasDebugConfigurationProvider(debugType: string, scope?: DebugConfigurationProviderScope): boolean {
if (scope === undefined) {
scope = DebugConfigurationProviderScope.Initial;
}
// check if there are providers for the given type that contribute a provideDebugConfigurations method
const providers = this.configProviders.filter(p => p.provideDebugConfigurations && (p.type === debugType) && (p.scope === scope));
return providers.length > 0;
}
async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise<IConfig | null | undefined> {
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<IConfig | null | undefined> {
// 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<any[]> {
await this.activateDebuggers('onDebugInitialConfigurations');
const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.scope === DebugConfigurationProviderScope.Initial && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token)));
return results.reduce((first, second) => first.concat(second), []);
}
async provideDynamicDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise<any[]> {
await this.activateDebuggers('onDebugDynamicConfigurations');
const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.scope === DebugConfigurationProviderScope.Dynamic && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token)));
return results.reduce((first, second) => first.concat(second), []);
}
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);
}
private registerListeners(): void {
debuggersExtPoint.setHandler((extensions, delta) => {
delta.added.forEach(added => {
added.value.forEach(rawAdapter => {
if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
}
if (rawAdapter.enableBreakpointsFor) {
rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
this.breakpointModeIdsSet.add(modeId);
});
}
if (rawAdapter.type !== '*') {
const existing = this.getDebugger(rawAdapter.type);
if (existing) {
existing.merge(rawAdapter, added.description);
} else {
this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
}
}
});
});
// take care of all wildcard contributions
extensions.forEach(extension => {
extension.value.forEach(rawAdapter => {
if (rawAdapter.type === '*') {
this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
}
});
});
delta.removed.forEach(removed => {
const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
});
// update the schema to include all attributes, snippets and types from extensions.
this.debuggers.forEach(adapter => {
const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
const schemaAttributes = adapter.getSchemaAttributes();
if (schemaAttributes && items.oneOf) {
items.oneOf.push(...schemaAttributes);
}
const configurationSnippets = adapter.configurationSnippets;
if (configurationSnippets && items.defaultSnippets) {
items.defaultSnippets.push(...configurationSnippets);
}
});
this.setCompoundSchemaValues();
this._onDidRegisterDebugger.fire();
});
breakpointsExtPoint.setHandler((extensions, delta) => {
delta.removed.forEach(removed => {
removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
});
delta.added.forEach(added => {
added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
});
});
this.toDispose.push(Event.any<IWorkspaceFoldersChangeEvent | WorkbenchState>(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();
}
}));
}
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.launches.push(this.instantiationService.createInstance(UserLaunch));
if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) {
this.selectConfiguration(undefined);
}
}
private setCompoundSchemaValues(): void {
const compoundConfigurationsSchema = (<IJSONSchema>launchSchema.properties!['compounds'].items).properties!['configurations'];
const launchNames = this.launches.map(l =>
l.getConfigurationNames(true)).reduce((first, second) => first.concat(second), []);
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![0].enum = launchNames;
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![1].properties!.name.enum = launchNames;
const folderNames = this.contextService.getWorkspace().folders.map(f => f.name);
(<IJSONSchema>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.filter(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()).pop();
}
get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined } {
return {
launch: this.selectedLaunch,
name: this.selectedName
};
}
get onDidSelectConfiguration(): Event<void> {
return this._onDidSelectConfigurationName.event;
}
getWorkspaceLaunch(): ILaunch | undefined {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
return this.launches[this.launches.length - 1];
}
return undefined;
}
selectConfiguration(launch: ILaunch | undefined, name?: string): void {
if (typeof launch === 'undefined') {
const rootUri = this.historyService.getLastActiveWorkspaceRoot();
launch = this.getLaunch(rootUri);
if (!launch || launch.getConfigurationNames().length === 0) {
launch = first(this.launches, 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);
} else {
this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
}
const names = launch ? launch.getConfigurationNames() : [];
if (name && names.indexOf(name) >= 0) {
this.setSelectedLaunchName(name);
}
if (!this.selectedName || names.indexOf(this.selectedName) === -1) {
this.setSelectedLaunchName(names.length ? names[0] : undefined);
}
const configuration = this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined;
if (configuration) {
this.debugConfigurationTypeContext.set(configuration.type);
} else {
this.debugConfigurationTypeContext.reset();
}
if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {
this._onDidSelectConfigurationName.fire();
}
}
canSetBreakpointsIn(model: ITextModel): boolean {
const modeId = model.getLanguageIdentifier().language;
if (!modeId || modeId === 'jsonc' || modeId === 'log') {
// do not allow breakpoints in our settings files and output
return false;
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
return true;
}
return this.breakpointModeIdsSet.has(modeId);
}
getDebugger(type: string): Debugger | undefined {
return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop();
}
isDebuggerInterestedInLanguage(language: string): boolean {
return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).length > 0;
}
async guessDebugger(type?: string): Promise<Debugger | undefined> {
if (type) {
const adapter = this.getDebugger(type);
return Promise.resolve(adapter);
}
const activeTextEditorControl = this.editorService.activeTextEditorControl;
let candidates: Debugger[] | undefined;
if (isCodeEditor(activeTextEditorControl)) {
const model = activeTextEditorControl.getModel();
const language = model ? model.getLanguageIdentifier().language : undefined;
const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
if (adapters.length === 1) {
return adapters[0];
}
if (adapters.length > 1) {
candidates = adapters;
}
}
if (!candidates) {
await this.activateDebuggers('onDebugInitialConfigurations');
candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider());
}
candidates.sort((first, second) => first.label.localeCompare(second.label));
const picks = candidates.map(c => ({ label: c.label, debugger: c }));
return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: nls.localize('more', "More..."), debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked && picked.debugger) {
return picked.debugger;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers');
}
return undefined;
});
}
async activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
const promises: Promise<any>[] = [
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);
} 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;
getCompound(name: string): ICompound | undefined {
const config = this.getConfig();
if (!config || !config.compounds) {
return undefined;
}
return config.compounds.filter(compound => compound.name === name).pop();
}
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;
}
return config.configurations.filter(config => config && config.name === name).shift();
}
get hidden(): boolean {
return false;
}
}
class Launch extends AbstractLaunch implements ILaunch {
constructor(
private configurationManager: ConfigurationManager,
public workspace: IWorkspaceFolder,
@IFileService private readonly fileService: IFileService,
@ITextFileService private readonly textFileService: ITextFileService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
}
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<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolderValue;
}
async openConfigFile(sideBySide: boolean, 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
const adapter = await this.configurationManager.guessDebugger(type);
if (adapter) {
const initialConfigs = await this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None);
content = await adapter.getInitialConfigurationContent(initialConfigs);
}
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
},
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
return ({
editor: withUndefinedAsNull(editor),
created
});
}
}
class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
constructor(
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
) {
super();
}
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<IGlobalConfig>('launch').workspaceValue;
}
async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> {
const editor = await this.editorService.openEditor({
resource: this.contextService.getWorkspace().configuration!,
options: { preserveFocus }
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
return ({
editor: withUndefinedAsNull(editor),
created: false
});
}
}
class UserLaunch extends AbstractLaunch implements ILaunch {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IPreferencesService private readonly preferencesService: IPreferencesService
) {
super();
}
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<IGlobalConfig>('launch').userValue;
}
async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> {
const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus });
return ({
editor: withUndefinedAsNull(editor),
created: false
});
}
}