diff --git a/package.json b/package.json index 4eade1bf39ca..906ad9e94b14 100644 --- a/package.json +++ b/package.json @@ -934,22 +934,6 @@ "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", "default": "${workspaceFolder}" }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "RedirectOutput", - "DebugStdLib", - "Django", - "Jinja", - "Sudo", - "Pyramid" - ] - }, - "default": [] - }, "env": { "type": "object", "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", @@ -974,25 +958,48 @@ "type": "boolean", "description": "Enable logging of debugger events to a log file.", "default": false + }, + "redirectOutput": { + "type": "boolean", + "description": "Redirect output.", + "default": true + }, + "debugStdLib": { + "type": "boolean", + "description": "Debug standard library code.", + "default": false + }, + "django": { + "type": "boolean", + "description": "Django debugging.", + "default": false + }, + "jinja": { + "enum": [ + true, + false, + null + ], + "description": "Jinja template debugging (e.g. Flask).", + "default": null + }, + "sudo": { + "type": "boolean", + "description": "Running debug program under elevated permissions (on Unix).", + "default": false + }, + "pyramid": { + "type": "boolean", + "description": "Whether debugging Pyramid applications", + "default": false } } }, "attach": { "required": [ - "port", - "remoteRoot" + "port" ], "properties": { - "localRoot": { - "type": "string", - "description": "Local source root that corrresponds to the 'remoteRoot'.", - "default": "${workspaceFolder}" - }, - "remoteRoot": { - "type": "string", - "description": "The source root of the remote host.", - "default": "" - }, "port": { "type": "number", "description": "Debug port to attach", @@ -1003,23 +1010,9 @@ "description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).", "default": "localhost" }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "RedirectOutput", - "DebugStdLib", - "Django", - "Jinja" - ] - }, - "default": [] - }, "pathMappings": { "type": "array", - "label": "Additional path mappings.", + "label": "Path mappings.", "items": { "type": "object", "label": "Path mapping", @@ -1031,7 +1024,7 @@ "localRoot": { "type": "string", "label": "Local source root.", - "default": "" + "default": "${workspaceFolder}" }, "remoteRoot": { "type": "string", @@ -1046,6 +1039,30 @@ "type": "boolean", "description": "Enable logging of debugger events to a log file.", "default": false + }, + "redirectOutput": { + "type": "boolean", + "description": "Redirect output.", + "default": true + }, + "debugStdLib": { + "type": "boolean", + "description": "Debug standard library code.", + "default": false + }, + "django": { + "type": "boolean", + "description": "Django debugging.", + "default": false + }, + "jinja": { + "enum": [ + true, + false, + null + ], + "description": "Jinja template debugging (e.g. Flask).", + "default": null } } } diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 12b2140c4f72..ca5a0ae8d4b3 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 989568fa731a..765c6c571caf 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -51,7 +51,23 @@ export interface ExceptionHandling { export type DebuggerType = 'python' | 'pythonExperimental'; -export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { +export interface AdditionalLaunchDebugOptions { + redirectOutput?: boolean; + django?: boolean; + jinja?: boolean; + debugStdLib?: boolean; + sudo?: boolean; + pyramid?: boolean; +} + +export interface AdditionalAttachDebugOptions { + redirectOutput?: boolean; + django?: boolean; + jinja?: boolean; + debugStdLib?: boolean; +} + +export interface BaseLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { type?: DebuggerType; /** An absolute path to the program to debug. */ module?: string; @@ -60,31 +76,42 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum /** Automatically stop target after launch. If not specified, target does not stop. */ stopOnEntry?: boolean; args: string[]; - applicationType?: string; cwd?: string; debugOptions?: DebugOptions[]; env?: Object; envFile: string; - exceptionHandling?: ExceptionHandling; console?: 'none' | 'integratedTerminal' | 'externalTerminal'; port?: number; host?: string; logToFile?: boolean; } -export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { +export interface LaunchRequestArgumentsV1 extends BaseLaunchRequestArguments { + exceptionHandling?: ExceptionHandling; +} + +export interface LaunchRequestArguments extends BaseLaunchRequestArguments, AdditionalLaunchDebugOptions { +} + +export interface BaseAttachRequestArguments extends DebugProtocol.AttachRequestArguments { type?: DebuggerType; /** An absolute path to local directory with source. */ - localRoot: string; - remoteRoot: string; port?: number; host?: string; - secret?: string; logToFile?: boolean; - pathMappings?: { localRoot: string; remoteRoot: string }[]; debugOptions?: DebugOptions[]; } +export interface AttachRequestArgumentsV1 extends BaseAttachRequestArguments { + secret?: string; + localRoot: string; + remoteRoot: string; +} +export interface AttachRequestArguments extends BaseAttachRequestArguments, AdditionalAttachDebugOptions { + localRoot?: string; + remoteRoot?: string; + pathMappings?: { localRoot: string; remoteRoot: string }[]; +} export interface IDebugServer { port: number; host?: string; diff --git a/src/client/debugger/DebugClients/RemoteDebugClient.ts b/src/client/debugger/DebugClients/RemoteDebugClient.ts index a7bee8e9f578..172b8ba79bd4 100644 --- a/src/client/debugger/DebugClients/RemoteDebugClient.ts +++ b/src/client/debugger/DebugClients/RemoteDebugClient.ts @@ -1,15 +1,15 @@ import { DebugSession } from 'vscode-debugadapter'; -import { AttachRequestArguments, IPythonProcess } from '../Common/Contracts'; +import { AttachRequestArgumentsV1, BaseAttachRequestArguments, IPythonProcess } from '../Common/Contracts'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; import { RemoteDebugServer } from '../DebugServers/RemoteDebugServer'; import { RemoteDebugServerV2 } from '../DebugServers/RemoteDebugServerv2'; import { DebugClient, DebugType } from './DebugClient'; -export class RemoteDebugClient extends DebugClient { +export class RemoteDebugClient extends DebugClient { private pythonProcess?: IPythonProcess; private debugServer?: BaseDebugServer; // tslint:disable-next-line:no-any - constructor(args: AttachRequestArguments, debugSession: DebugSession) { + constructor(args: T, debugSession: DebugSession) { super(args, debugSession); } @@ -19,7 +19,7 @@ export class RemoteDebugClient extends DebugClient { this.debugServer = new RemoteDebugServerV2(this.debugSession, undefined as any, this.args); } else { this.pythonProcess = pythonProcess!; - this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args); + this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args as {} as AttachRequestArgumentsV1); } return this.debugServer!; } diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 417efba39e26..9028cb10f01e 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -22,7 +22,7 @@ export class LocalDebugClientV2 extends LocalDebugClient { return ['-m', this.args.module, ...programArgs]; } if (this.args.program && this.args.program.length > 0) { - return ['--file', this.args.program, ...programArgs]; + return [this.args.program, ...programArgs]; } return programArgs; } diff --git a/src/client/debugger/DebugServers/RemoteDebugServer.ts b/src/client/debugger/DebugServers/RemoteDebugServer.ts index 32d93d004920..b63fa9955484 100644 --- a/src/client/debugger/DebugServers/RemoteDebugServer.ts +++ b/src/client/debugger/DebugServers/RemoteDebugServer.ts @@ -1,8 +1,8 @@ -// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template +// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template no-this-assignment "use strict"; import { DebugSession, OutputEvent } from "vscode-debugadapter"; -import { IPythonProcess, IDebugServer, AttachRequestArguments } from "../Common/Contracts"; +import { IPythonProcess, IDebugServer, AttachRequestArgumentsV1 } from "../Common/Contracts"; import * as net from "net"; import { BaseDebugServer } from "./BaseDebugServer"; import { SocketStream } from "../../common/net/socket/SocketStream"; @@ -15,8 +15,8 @@ const AttachCommandBytes: Buffer = new Buffer("ATCH", "ascii"); export class RemoteDebugServer extends BaseDebugServer { private socket?: net.Socket; - private args: AttachRequestArguments; - constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArguments) { + private args: AttachRequestArgumentsV1; + constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArgumentsV1) { super(debugSession, pythonProcess); this.args = args; } @@ -29,7 +29,7 @@ export class RemoteDebugServer extends BaseDebugServer { catch (ex) { } this.socket = undefined; } - private stream: SocketStream; + private stream!: SocketStream; public Start(): Promise { return new Promise((resolve, reject) => { let that = this; diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 190330501a52..316b1aed417d 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -14,9 +14,9 @@ import { DebugProtocol } from "vscode-debugprotocol"; import { DEBUGGER } from '../../client/telemetry/constants'; import { DebuggerTelemetry } from '../../client/telemetry/types'; import { isNotInstalledError } from '../common/helpers'; -import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind } from "./Common/Contracts"; +import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind, LaunchRequestArgumentsV1, AttachRequestArgumentsV1 } from "./Common/Contracts"; import { IDebugServer, IPythonEvaluationResult, IPythonModule, IPythonStackFrame, IPythonThread } from "./Common/Contracts"; -import { AttachRequestArguments, DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; +import { DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; import { getPythonExecutable, validatePath } from './Common/Utils'; import { DebugClient } from "./DebugClients/DebugClient"; import { CreateAttachDebugClient, CreateLaunchDebugClient } from "./DebugClients/DebugFactory"; @@ -203,8 +203,8 @@ export class PythonDebugger extends LoggingDebugSession { this.sendEvent(new OutputEvent(output, outputChannel)); } private entryResponse?: DebugProtocol.LaunchResponse; - private launchArgs!: LaunchRequestArguments; - private attachArgs!: AttachRequestArguments; + private launchArgs!: LaunchRequestArgumentsV1; + private attachArgs!: AttachRequestArgumentsV1; private canStartDebugger(): Promise { return Promise.resolve(true); } @@ -280,7 +280,7 @@ export class PythonDebugger extends LoggingDebugSession { this.sendErrorResponse(response, 200, errorMsg); }); } - protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) { + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArgumentsV1) { if (args.logToFile === true) { logger.setup(LogLevel.Verbose, true); } diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 849994a735b1..d1395d8dda6b 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -3,6 +3,8 @@ 'use strict'; +// tslint:disable:no-invalid-template-strings + import { injectable, unmanaged } from 'inversify'; import * as path from 'path'; import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, WorkspaceFolder } from 'vscode'; @@ -11,23 +13,21 @@ import { PythonLanguage } from '../../common/constants'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { AttachRequestArguments, DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; - -// tslint:disable:no-invalid-template-strings +import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType, DebugOptions } from '../Common/Contracts'; -export type PythonLaunchDebugConfiguration = DebugConfiguration & LaunchRequestArguments; -export type PythonAttachDebugConfiguration = DebugConfiguration & AttachRequestArguments; +export type PythonLaunchDebugConfiguration = DebugConfiguration & T; +export type PythonAttachDebugConfiguration = DebugConfiguration & T; @injectable() -export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { +export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { constructor(@unmanaged() public debugType: DebuggerType, protected serviceContainer: IServiceContainer) { } public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { const workspaceFolder = this.getWorkspaceFolder(folder); if (debugConfiguration.request === 'attach') { - this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); + this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); } else { - const config = debugConfiguration as PythonLaunchDebugConfiguration; + const config = debugConfiguration as PythonLaunchDebugConfiguration; const numberOfSettings = Object.keys(config); if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { @@ -42,24 +42,22 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro this.provideLaunchDefaults(workspaceFolder, config); } + + const dbgConfig = (debugConfiguration as (BaseLaunchRequestArguments | BaseAttachRequestArguments)); + if (Array.isArray(dbgConfig.debugOptions)) { + dbgConfig.debugOptions = dbgConfig.debugOptions!.filter((item, pos) => dbgConfig.debugOptions!.indexOf(item) === pos); + } return debugConfiguration; } - protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { if (!Array.isArray(debugConfiguration.debugOptions)) { debugConfiguration.debugOptions = []; } - // Always redirect output. - if (debugConfiguration.debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.RedirectOutput); - } if (!debugConfiguration.host) { debugConfiguration.host = 'localhost'; } - if (!debugConfiguration.localRoot && workspaceFolder) { - debugConfiguration.localRoot = workspaceFolder.fsPath; - } } - protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; @@ -122,7 +120,7 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro return editor.document.fileName; } } - private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { + private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { if (!debugConfiguration) { return; } diff --git a/src/client/debugger/configProviders/pythonProvider.ts b/src/client/debugger/configProviders/pythonProvider.ts index 1b349bf2c465..69b2fe743319 100644 --- a/src/client/debugger/configProviders/pythonProvider.ts +++ b/src/client/debugger/configProviders/pythonProvider.ts @@ -4,12 +4,25 @@ 'use strict'; import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; -import { BaseConfigurationProvider } from './baseProvider'; +import { AttachRequestArgumentsV1, DebugOptions, LaunchRequestArgumentsV1 } from '../Common/Contracts'; +import { BaseConfigurationProvider, PythonAttachDebugConfiguration } from './baseProvider'; @injectable() -export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { +export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('python', serviceContainer); } + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + super.provideAttachDefaults(workspaceFolder, debugConfiguration); + const debugOptions = debugConfiguration.debugOptions!; + // Always redirect output. + if (debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { + debugOptions.push(DebugOptions.RedirectOutput); + } + if (!debugConfiguration.localRoot && workspaceFolder) { + debugConfiguration.localRoot = workspaceFolder.fsPath; + } + } } diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 47e587daabc7..e8953534c1b0 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -7,50 +7,81 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; -import { DebugOptions } from '../Common/Contracts'; +import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; @injectable() -export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { +export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('pythonExperimental', serviceContainer); } - protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { super.provideLaunchDefaults(workspaceFolder, debugConfiguration); - - debugConfiguration.stopOnEntry = false; - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; - - // Add PTVSD specific flags. + const debugOptions = debugConfiguration.debugOptions!; + if (debugConfiguration.debugStdLib) { + this.debugOption(debugOptions, DebugOptions.DebugStdLib); + } + if (debugConfiguration.django) { + this.debugOption(debugOptions, DebugOptions.Django); + } + if (debugConfiguration.jinja) { + this.debugOption(debugOptions, DebugOptions.Jinja); + } + if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { + this.debugOption(debugOptions, DebugOptions.RedirectOutput); + } + if (debugConfiguration.sudo) { + this.debugOption(debugOptions, DebugOptions.Sudo); + } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' - && debugConfiguration.debugOptions.indexOf(DebugOptions.Jinja) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.Jinja); + && debugOptions.indexOf(DebugOptions.Jinja) === -1 + && debugConfiguration.jinja !== false) { + this.debugOption(debugOptions, DebugOptions.Jinja); } } - protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { + protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { super.provideAttachDefaults(workspaceFolder, debugConfiguration); - - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; + const debugOptions = debugConfiguration.debugOptions!; + if (debugConfiguration.debugStdLib) { + this.debugOption(debugOptions, DebugOptions.DebugStdLib); + } + if (debugConfiguration.django) { + this.debugOption(debugOptions, DebugOptions.Django); + } + if (debugConfiguration.jinja) { + this.debugOption(debugOptions, DebugOptions.Jinja); + } + if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { + this.debugOption(debugOptions, DebugOptions.RedirectOutput); + } // We'll need paths to be fixed only in the case where local and remote hosts are the same // I.e. only if hostName === 'localhost' or '127.0.0.1' or '' const isLocalHost = !debugConfiguration.host || debugConfiguration.host === 'localhost' || debugConfiguration.host === '127.0.0.1'; if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { - debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions.push(DebugOptions.WindowsClient); + this.debugOption(debugOptions, DebugOptions.WindowsClient); } if (!debugConfiguration.pathMappings) { debugConfiguration.pathMappings = []; } - debugConfiguration.pathMappings!.push({ - localRoot: debugConfiguration.localRoot, - remoteRoot: debugConfiguration.remoteRoot - }); + if (debugConfiguration.localRoot && debugConfiguration.remoteRoot) { + debugConfiguration.pathMappings!.push({ + localRoot: debugConfiguration.localRoot, + remoteRoot: debugConfiguration.remoteRoot + }); + } + } + private debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { + if (debugOptions.indexOf(debugOption) >= 0) { + return; + } + debugOptions.push(debugOption); } } diff --git a/src/client/extension.ts b/src/client/extension.ts index ed1b0d09443b..627cf7c39385 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -26,6 +26,7 @@ import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { StopWatch } from './common/stopWatch'; import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; import { IDebugConfigurationProvider } from './debugger/types'; @@ -153,7 +154,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new TerminalProvider(serviceContainer)); context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + type ConfigurationProvider = BaseConfigurationProvider; + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); }); activationDeferred.resolve(); diff --git a/src/test/debugger/attach.test.ts b/src/test/debugger/attach.test.ts index 7747a0450c01..c03e0cdf3573 100644 --- a/src/test/debugger/attach.test.ts +++ b/src/test/debugger/attach.test.ts @@ -11,7 +11,7 @@ import { DebugClient } from 'vscode-debugadapter-testsupport'; import { createDeferred } from '../../client/common/helpers'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; -import { AttachRequestArguments } from '../../client/debugger/Common/Contracts'; +import { AttachRequestArgumentsV1 } from '../../client/debugger/Common/Contracts'; import { PYTHON_PATH, sleep } from '../common'; import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -53,7 +53,7 @@ suite('Attach Debugger', () => { } const port = await getFreePort({ host: 'localhost', port: 3000 }); - const args: AttachRequestArguments = { + const args: AttachRequestArgumentsV1 = { localRoot: path.dirname(fileToDebug), remoteRoot: path.dirname(fileToDebug), port: port, diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 30d1a4192800..f470616ee7a5 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -3,7 +3,7 @@ 'use strict'; -// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion +// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion no-invalid-this import { expect } from 'chai'; import * as path from 'path'; @@ -83,9 +83,11 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { const pythonFile = 'xyz.py'; @@ -98,10 +100,12 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { setupActiveEditor(undefined, PythonLanguage.language); @@ -111,9 +115,11 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.not.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.not.have.property('localRoot'); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and non python file', async () => { const activeFile = 'xyz.js'; @@ -125,9 +131,9 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); expect(debugConfig).to.not.have.property('localRoot'); expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); }); test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { const activeFile = 'xyz.py'; @@ -140,10 +146,12 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + } }); test('Ensure \'localRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; @@ -156,6 +164,43 @@ enum OS { const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, request: 'attach' } as any as DebugConfiguration); expect(debugConfig).to.have.property('localRoot', localRoot); + if (provider.debugType === 'pythonExperimental') { + expect(debugConfig!.pathMappings).to.be.lengthOf(0); + } + }); + test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { + if (provider.debugType !== 'pythonExperimental') { + return this.skip(); + } + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig!.pathMappings).to.be.lengthOf(1); + expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); + }); + test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { + if (provider.debugType !== 'pythonExperimental') { + return this.skip(); + } + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig!.pathMappings).to.be.lengthOf(1); + expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); }); test('Ensure \'remoteRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; @@ -189,9 +234,10 @@ enum OS { setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); + const expectedDebugOptions = debugOptions.slice(); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugOptions, request: 'attach' } as any as DebugConfiguration); - expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(debugOptions); + expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(expectedDebugOptions); }); }); });