Path: blob/main/src/vs/workbench/contrib/debug/common/debugger.ts
5237 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as nls from '../../../../nls.js';6import { isObject } from '../../../../base/common/types.js';7import { IJSONSchema, IJSONSchemaMap, IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js';8import { IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';9import { IConfig, IDebuggerContribution, IDebugAdapter, IDebugger, IDebugSession, IAdapterManager, IDebugService, debuggerDisabledMessage, IDebuggerMetadata, DebugConfigurationProviderTriggerKind } from './debug.js';10import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';11import { IConfigurationResolverService } from '../../../services/configurationResolver/common/configurationResolver.js';12import * as ConfigurationResolverUtils from '../../../services/configurationResolver/common/configurationResolverUtils.js';13import { ITextResourcePropertiesService } from '../../../../editor/common/services/textResourceConfiguration.js';14import { URI } from '../../../../base/common/uri.js';15import { Schemas } from '../../../../base/common/network.js';16import { isDebuggerMainContribution } from './debugUtils.js';17import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';18import { ITelemetryEndpoint } from '../../../../platform/telemetry/common/telemetry.js';19import { cleanRemoteAuthority } from '../../../../platform/telemetry/common/telemetryUtils.js';20import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';21import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';22import { filter } from '../../../../base/common/objects.js';23import { IProductService } from '../../../../platform/product/common/productService.js';2425export class Debugger implements IDebugger, IDebuggerMetadata {2627private debuggerContribution: IDebuggerContribution;28private mergedExtensionDescriptions: IExtensionDescription[] = [];29private mainExtensionDescription: IExtensionDescription | undefined;3031private debuggerWhen: ContextKeyExpression | undefined;32private debuggerHiddenWhen: ContextKeyExpression | undefined;3334constructor(35private adapterManager: IAdapterManager,36dbgContribution: IDebuggerContribution,37extensionDescription: IExtensionDescription,38@IConfigurationService private readonly configurationService: IConfigurationService,39@ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService,40@IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService,41@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,42@IDebugService private readonly debugService: IDebugService,43@IContextKeyService private readonly contextKeyService: IContextKeyService,44@IProductService private readonly productService: IProductService,45) {46this.debuggerContribution = { type: dbgContribution.type };47this.merge(dbgContribution, extensionDescription);4849this.debuggerWhen = typeof this.debuggerContribution.when === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.when) : undefined;50this.debuggerHiddenWhen = typeof this.debuggerContribution.hiddenWhen === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.hiddenWhen) : undefined;51}5253merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void {5455/**56* Copies all properties of source into destination. The optional parameter "overwrite" allows to control57* if existing non-structured properties on the destination should be overwritten or not. Defaults to true (overwrite).58*/59function mixin(destination: any, source: any, overwrite: boolean, level = 0): any {6061if (!isObject(destination)) {62return source;63}6465if (isObject(source)) {66Object.keys(source).forEach(key => {67if (key !== '__proto__') {68if (isObject(destination[key]) && isObject(source[key])) {69mixin(destination[key], source[key], overwrite, level + 1);70} else {71if (key in destination) {72if (overwrite) {73if (level === 0 && key === 'type') {74// don't merge the 'type' property75} else {76destination[key] = source[key];77}78}79} else {80destination[key] = source[key];81}82}83}84});85}8687return destination;88}8990// only if not already merged91if (this.mergedExtensionDescriptions.indexOf(extensionDescription) < 0) {9293// remember all extensions that have been merged for this debugger94this.mergedExtensionDescriptions.push(extensionDescription);9596// merge new debugger contribution into existing contributions (and don't overwrite values in built-in extensions)97mixin(this.debuggerContribution, otherDebuggerContribution, extensionDescription.isBuiltin);9899// remember the extension that is considered the "main" debugger contribution100if (isDebuggerMainContribution(otherDebuggerContribution)) {101this.mainExtensionDescription = extensionDescription;102}103}104}105106async startDebugging(configuration: IConfig, parentSessionId: string): Promise<boolean> {107const parentSession = this.debugService.getModel().getSession(parentSessionId);108return await this.debugService.startDebugging(undefined, configuration, { parentSession }, undefined);109}110111async createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter> {112await this.adapterManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type);113const da = this.adapterManager.createDebugAdapter(session);114if (da) {115return Promise.resolve(da);116}117throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));118}119120async substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {121const substitutedConfig = await this.adapterManager.substituteVariables(this.type, folder, config);122return await this.configurationResolverService.resolveWithInteractionReplace(folder, substitutedConfig, 'launch', this.variables, substitutedConfig.__configurationTarget);123}124125runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, sessionId: string): Promise<number | undefined> {126return this.adapterManager.runInTerminal(this.type, args, sessionId);127}128129get label(): string {130return this.debuggerContribution.label || this.debuggerContribution.type;131}132133get type(): string {134return this.debuggerContribution.type;135}136137get variables(): { [key: string]: string } | undefined {138return this.debuggerContribution.variables;139}140141get configurationSnippets(): IJSONSchemaSnippet[] | undefined {142return this.debuggerContribution.configurationSnippets;143}144145get languages(): string[] | undefined {146return this.debuggerContribution.languages;147}148149get when(): ContextKeyExpression | undefined {150return this.debuggerWhen;151}152153get hiddenWhen(): ContextKeyExpression | undefined {154return this.debuggerHiddenWhen;155}156157get enabled() {158return !this.debuggerWhen || this.contextKeyService.contextMatchesRules(this.debuggerWhen);159}160161get isHiddenFromDropdown() {162if (!this.debuggerHiddenWhen) {163return false;164}165return this.contextKeyService.contextMatchesRules(this.debuggerHiddenWhen);166}167168get strings() {169return this.debuggerContribution.strings ?? this.debuggerContribution.uiMessages;170}171172interestedInLanguage(languageId: string): boolean {173return !!(this.languages && this.languages.indexOf(languageId) >= 0);174}175176hasInitialConfiguration(): boolean {177return !!this.debuggerContribution.initialConfigurations;178}179180hasDynamicConfigurationProviders(): boolean {181return this.debugService.getConfigurationManager().hasDebugConfigurationProvider(this.type, DebugConfigurationProviderTriggerKind.Dynamic);182}183184hasConfigurationProvider(): boolean {185return this.debugService.getConfigurationManager().hasDebugConfigurationProvider(this.type);186}187188getInitialConfigurationContent(initialConfigs?: IConfig[]): Promise<string> {189// at this point we got some configs from the package.json and/or from registered DebugConfigurationProviders190let initialConfigurations = this.debuggerContribution.initialConfigurations || [];191if (initialConfigs) {192initialConfigurations = initialConfigurations.concat(initialConfigs);193}194195const eol = this.resourcePropertiesService.getEOL(URI.from({ scheme: Schemas.untitled, path: '1' })) === '\r\n' ? '\r\n' : '\n';196const configs = JSON.stringify(initialConfigurations, null, '\t').split('\n').map(line => '\t' + line).join(eol).trim();197const comment1 = nls.localize('launch.config.comment1', "Use IntelliSense to learn about possible attributes.");198const comment2 = nls.localize('launch.config.comment2', "Hover to view descriptions of existing attributes.");199const comment3 = nls.localize('launch.config.comment3', "For more information, visit: {0}", 'https://go.microsoft.com/fwlink/?linkid=830387');200201let content = [202'{',203`\t// ${comment1}`,204`\t// ${comment2}`,205`\t// ${comment3}`,206`\t"version": "0.2.0",`,207`\t"configurations": ${configs}`,208'}'209].join(eol);210211// fix formatting212const editorConfig = this.configurationService.getValue<any>();213if (editorConfig.editor && editorConfig.editor.insertSpaces) {214content = content.replace(new RegExp('\t', 'g'), ' '.repeat(editorConfig.editor.tabSize));215}216217return Promise.resolve(content);218}219220getMainExtensionDescriptor(): IExtensionDescription {221return this.mainExtensionDescription || this.mergedExtensionDescriptions[0];222}223224getCustomTelemetryEndpoint(): ITelemetryEndpoint | undefined {225const aiKey = this.debuggerContribution.aiKey;226if (!aiKey) {227return undefined;228}229230const sendErrorTelemtry = cleanRemoteAuthority(this.environmentService.remoteAuthority, this.productService) !== 'other';231return {232id: `${this.getMainExtensionDescriptor().publisher}.${this.type}`,233aiKey,234sendErrorTelemetry: sendErrorTelemtry235};236}237238getSchemaAttributes(definitions: IJSONSchemaMap): IJSONSchema[] | null {239240if (!this.debuggerContribution.configurationAttributes) {241return null;242}243244// fill in the default configuration attributes shared by all adapters.245return Object.entries(this.debuggerContribution.configurationAttributes).map(([request, attributes]) => {246const definitionId = `${this.type}:${request}`;247const platformSpecificDefinitionId = `${this.type}:${request}:platform`;248const defaultRequired = ['name', 'type', 'request'];249attributes.required = attributes.required && attributes.required.length ? defaultRequired.concat(attributes.required) : defaultRequired;250attributes.additionalProperties = false;251attributes.type = 'object';252if (!attributes.properties) {253attributes.properties = {};254}255const properties = attributes.properties;256properties['type'] = {257enum: [this.type],258enumDescriptions: [this.label],259description: nls.localize('debugType', "Type of configuration."),260pattern: '^(?!node2)',261deprecationMessage: this.debuggerContribution.deprecated || (this.enabled ? undefined : debuggerDisabledMessage(this.type)),262doNotSuggest: !!this.debuggerContribution.deprecated,263errorMessage: nls.localize('debugTypeNotRecognised', "The debug type is not recognized. Make sure that you have a corresponding debug extension installed and that it is enabled."),264patternErrorMessage: nls.localize('node2NotSupported', "\"node2\" is no longer supported, use \"node\" instead and set the \"protocol\" attribute to \"inspector\".")265};266properties['request'] = {267enum: [request],268description: nls.localize('debugRequest', "Request type of configuration. Can be \"launch\" or \"attach\"."),269};270for (const prop in definitions['common'].properties) {271properties[prop] = {272$ref: `#/definitions/common/properties/${prop}`273};274}275Object.keys(properties).forEach(name => {276// Use schema allOf property to get independent error reporting #21113277ConfigurationResolverUtils.applyDeprecatedVariableMessage(properties[name]);278});279280definitions[definitionId] = { ...attributes };281definitions[platformSpecificDefinitionId] = {282type: 'object',283additionalProperties: false,284properties: filter(properties, key => key !== 'type' && key !== 'request' && key !== 'name')285};286287// Don't add the OS props to the real attributes object so they don't show up in 'definitions'288const attributesCopy = { ...attributes };289attributesCopy.properties = {290...properties,291...{292windows: {293$ref: `#/definitions/${platformSpecificDefinitionId}`,294description: nls.localize('debugWindowsConfiguration', "Windows specific launch configuration attributes."),295},296osx: {297$ref: `#/definitions/${platformSpecificDefinitionId}`,298description: nls.localize('debugOSXConfiguration', "OS X specific launch configuration attributes."),299},300linux: {301$ref: `#/definitions/${platformSpecificDefinitionId}`,302description: nls.localize('debugLinuxConfiguration', "Linux specific launch configuration attributes."),303}304}305};306307return attributesCopy;308});309}310}311312313