diff --git a/package.json b/package.json index 599d2271..11f38cf5 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,64 @@ "scope": "resource", "type": "boolean" }, + "debugpy.debugVariablePresentation": { + "default": {}, + "description": "%debugpy.debugVariablePresentation.description%", + "scope": "resource", + "type": "object", + "properties": { + "all": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.all.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "class": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.class.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "function": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.function.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "protected": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.protected.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "special": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.special.description%", + "enum": [ + "inline", + "group", + "hide" + ] + } + } + }, "debugpy.showPythonInlineValues": { "default": false, "description": "%debugpy.showPythonInlineValues.description%", @@ -171,6 +229,11 @@ ], "type": "object" }, + "consoleName": { + "default": "Python Debug Console", + "description": "Display name of the debug console or terminal", + "type": "string" + }, "debugAdapterPath": { "description": "Path (fully qualified) to the python debug adapter executable.", "type": "string" @@ -275,10 +338,61 @@ "description": "Whether to enable Sub Process debugging", "type": "boolean" }, - "consoleName": { - "default": "Python Debug Console", - "description": "Display name of the debug console or terminal", - "type": "string" + "variablePresentation": { + "default": {}, + "description": "%debugpy.debugVariablePresentation.description%", + "properties": { + "all": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.all.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "class": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.class.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "function": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.function.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "protected": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.protected.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "special": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.special.description%", + "enum": [ + "inline", + "group", + "hide" + ] + } + } } } }, @@ -342,6 +456,11 @@ }, "type": "object" }, + "autoStartBrowser": { + "default": false, + "description": "Open external browser to launch the application", + "type": "boolean" + }, "console": { "default": "integratedTerminal", "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", @@ -351,6 +470,11 @@ "internalConsole" ] }, + "consoleName": { + "default": "Python Debug Console", + "description": "Display name of the debug console or terminal", + "type": "string" + }, "cwd": { "default": "${workspaceFolder}", "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", @@ -360,11 +484,6 @@ "description": "Path (fully qualified) to the Python debug adapter executable.", "type": "string" }, - "autoStartBrowser": { - "default": false, - "description": "Open external browser to launch the application", - "type": "boolean" - }, "django": { "default": false, "description": "Django debugging.", @@ -388,6 +507,11 @@ "description": "Enable debugging of gevent monkey-patched code.", "type": "boolean" }, + "guiEventLoop": { + "default": "matplotlib", + "description": "The GUI event loop that's going to run. Possible values: \"matplotlib\", \"wx\", \"qt\", \"none\", or a custom function that'll be imported and run.", + "type": "string" + }, "jinja": { "default": null, "description": "Jinja template debugging (e.g. Flask).", @@ -500,15 +624,61 @@ "description": "Running debug program under elevated permissions (on Unix).", "type": "boolean" }, - "guiEventLoop": { - "default": "matplotlib", - "description": "The GUI event loop that's going to run. Possible values: \"matplotlib\", \"wx\", \"qt\", \"none\", or a custom function that'll be imported and run.", - "type": "string" - }, - "consoleName": { - "default": "Python Debug Console", - "description": "Display name of the debug console or terminal", - "type": "string" + "variablePresentation": { + "default": {}, + "description": "%debugpy.debugVariablePresentation.description%", + "properties": { + "all": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.all.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "class": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.class.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "function": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.function.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "protected": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.protected.description%", + "enum": [ + "inline", + "group", + "hide" + ] + }, + "special": { + "type": "string", + "default": "group", + "description" : "%debugpy.debugVariablePresentation.special.description%", + "enum": [ + "inline", + "group", + "hide" + ] + } + } } } } diff --git a/package.nls.json b/package.nls.json index 5a53eb61..7a9ce471 100644 --- a/package.nls.json +++ b/package.nls.json @@ -5,5 +5,11 @@ "debugpy.command.reportIssue.title": "Report Issue...", "debugpy.command.viewOutput.title": "Show Output", "debugpy.debugJustMyCode.description": "When debugging only step through user-written code. Disable this to allow stepping into library code.", + "debugpy.debugVariablePresentation.description": "Allow the user to specify the preferred presentation of variables.", + "debugpy.debugVariablePresentation.all.description": "Set preferred presentation of variables for all instances.", + "debugpy.debugVariablePresentation.class.description": "Set Class presentation of variables.", + "debugpy.debugVariablePresentation.function.description": "Set Function preferred presentation of variables.", + "debugpy.debugVariablePresentation.protected.description": "Set Protected preferred presentation of variables.", + "debugpy.debugVariablePresentation.special.description": "Set Special preferred presentation of variables.", "debugpy.showPythonInlineValues.description": "Whether to display inline values in the editor while debugging." } diff --git a/src/extension/debugger/configuration/resolvers/attach.ts b/src/extension/debugger/configuration/resolvers/attach.ts index b2446183..00b7a5f6 100644 --- a/src/extension/debugger/configuration/resolvers/attach.ts +++ b/src/extension/debugger/configuration/resolvers/attach.ts @@ -50,6 +50,12 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver( + 'debugVariablePresentation', + {}, + ); + } debugConfiguration.showReturnValue = debugConfiguration.showReturnValue !== false; // Pass workspace folder so we can get this when we get debug events firing. debugConfiguration.workspaceFolder = workspaceFolder ? workspaceFolder.fsPath : undefined; diff --git a/src/extension/debugger/configuration/resolvers/launch.ts b/src/extension/debugger/configuration/resolvers/launch.ts index 257c72fd..3673fd26 100644 --- a/src/extension/debugger/configuration/resolvers/launch.ts +++ b/src/extension/debugger/configuration/resolvers/launch.ts @@ -114,6 +114,12 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver( + 'debugVariablePresentation', + {}, + ); + } // Pass workspace folder so we can get this when we get debug events firing. debugConfiguration.workspaceFolder = workspaceFolder ? workspaceFolder.fsPath : undefined; const debugOptions = debugConfiguration.debugOptions!; diff --git a/src/test/unittest/configuration/resolvers/attach.unit.test.ts b/src/test/unittest/configuration/resolvers/attach.unit.test.ts index 2994e751..28a0f57a 100644 --- a/src/test/unittest/configuration/resolvers/attach.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/attach.unit.test.ts @@ -74,6 +74,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { return debugpySettings.object; } + function createVariablePresentationMoqConfiguration(variablePresentation: object) { + const debugpySettings = TypeMoq.Mock.ofType(); + debugpySettings + .setup((p) => p.get('debugVariablePresentation', TypeMoq.It.isAny())) + .returns(() => variablePresentation); + return debugpySettings.object; + } + function setupActiveEditor(fileName: string | undefined, languageId: string) { if (fileName) { const textEditor = TypeMoq.Mock.ofType(); @@ -566,6 +574,61 @@ getInfoPerOS().forEach(([osName, osType, path]) => { }); }); + const testsForVariablePresentation = [ + { + variablePresentation: {}, + variablePresentationSetting: { + class: 'inline', + }, + expectedResult: {}, + }, + { + variablePresentation: { + class: 'inline', + }, + variablePresentationSetting: { + class: 'hide', + }, + expectedResult: { + class: 'inline', + }, + }, + { + variablePresentation: undefined, + variablePresentationSetting: { + class: 'inline', + }, + expectedResult: { + class: 'inline', + }, + }, + ]; + testsForVariablePresentation.forEach(async (testParams) => { + test('Ensure variablePresentation property is correctly derived from global settings', async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugOptions = debugOptionsAvailable + .slice() + .concat(DebugOptions.Jinja, DebugOptions.Sudo) as DebugOptions[]; + + getConfigurationStub + .withArgs('debugpy', sinon.match.any) + .returns(createVariablePresentationMoqConfiguration(testParams.variablePresentationSetting)); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, + debugOptions, + variablePresentation: testParams.variablePresentation, + }); + expect(debugConfig) + .to.have.property('variablePresentation') + .that.deep.equals(testParams.expectedResult); // Corrected to use deep.equals + }); + }); + test('Send consoleName value to debugpy as consoleTitle', async () => { const activeFile = 'xyz.py'; const consoleName = 'My Console Name'; diff --git a/src/test/unittest/configuration/resolvers/launch.unit.test.ts b/src/test/unittest/configuration/resolvers/launch.unit.test.ts index 29c2c313..1fef6e82 100644 --- a/src/test/unittest/configuration/resolvers/launch.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/launch.unit.test.ts @@ -72,6 +72,14 @@ getInfoPerOS().forEach(([osName, osType, path]) => { return debugpySettings.object; } + function createVariablePresentationMoqConfiguration(variablePresentation: object) { + const debugpySettings = TypeMoq.Mock.ofType(); + debugpySettings + .setup((p) => p.get('debugVariablePresentation', TypeMoq.It.isAny())) + .returns(() => variablePresentation); + return debugpySettings.object; + } + function getClientOS() { return osType === platform.OSType.Windows ? 'windows' : 'unix'; } @@ -803,6 +811,55 @@ getInfoPerOS().forEach(([osName, osType, path]) => { }); }); + const testsForVariablePresentation = [ + { + variablePresentation: {}, + variablePresentationSetting: { + class: 'inline', + }, + expectedResult: {}, + }, + { + variablePresentation: { + class: 'inline', + }, + variablePresentationSetting: { + class: 'hide', + }, + expectedResult: { + class: 'inline', + }, + }, + { + variablePresentation: undefined, + variablePresentationSetting: { + class: 'inline', + }, + expectedResult: { + class: 'inline', + }, + }, + ]; + testsForVariablePresentation.forEach(async (testParams) => { + test('Ensure variablePresentation property is correctly derived from global settings', async () => { + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + getConfigurationStub + .withArgs('debugpy', sinon.match.any) + .returns(createVariablePresentationMoqConfiguration(testParams.variablePresentationSetting)); + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, + variablePresentation: testParams.variablePresentation, + }); + expect(debugConfig) + .to.have.property('variablePresentation') + .that.deep.equals(testParams.expectedResult); // Corrected to use deep.equals + }); + }); + const testsForRedirectOutput = [ { console: 'internalConsole',