From a85a6230ce196f41c5c534958b79a440a50cc6b5 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Fri, 19 Apr 2024 16:07:18 -0700 Subject: [PATCH 1/5] Add consoleName as arg --- package.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 3fc1c3e6..4bb21344 100644 --- a/package.json +++ b/package.json @@ -263,6 +263,11 @@ "default": false, "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" } } }, @@ -335,10 +340,6 @@ "internalConsole" ] }, - "consoleTitle": { - "default": "Python Debug Console", - "description": "Display name of the debug console or terminal" - }, "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).", @@ -492,6 +493,11 @@ "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" } } } From 51b939c251e2b6173517e83ac003b3cb3e2a3884 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Fri, 19 Apr 2024 16:07:53 -0700 Subject: [PATCH 2/5] Update consoleName before sent it to debugpy --- src/extension/debugger/configuration/resolvers/base.ts | 4 ++++ src/extension/debugger/configuration/resolvers/launch.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/extension/debugger/configuration/resolvers/base.ts b/src/extension/debugger/configuration/resolvers/base.ts index 7775ae0d..160c6fc5 100644 --- a/src/extension/debugger/configuration/resolvers/base.ts +++ b/src/extension/debugger/configuration/resolvers/base.ts @@ -41,6 +41,10 @@ export abstract class BaseConfigurationResolver if (debugConfiguration.clientOS === undefined) { debugConfiguration.clientOS = getOSType() === OSType.Windows ? 'windows' : 'unix'; } + if (debugConfiguration.consoleName) { + debugConfiguration.consoleTitle = debugConfiguration.consoleName; + delete debugConfiguration.consoleName; + } return debugConfiguration as T; } diff --git a/src/extension/debugger/configuration/resolvers/launch.ts b/src/extension/debugger/configuration/resolvers/launch.ts index 474ba04c..423d5d9d 100644 --- a/src/extension/debugger/configuration/resolvers/launch.ts +++ b/src/extension/debugger/configuration/resolvers/launch.ts @@ -39,6 +39,10 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver Date: Fri, 19 Apr 2024 16:08:02 -0700 Subject: [PATCH 3/5] Add tests --- .../resolvers/attach.unit.test.ts | 20 +++++++++++++++++++ .../resolvers/launch.unit.test.ts | 15 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/test/unittest/configuration/resolvers/attach.unit.test.ts b/src/test/unittest/configuration/resolvers/attach.unit.test.ts index 9fe63d2f..e9353acc 100644 --- a/src/test/unittest/configuration/resolvers/attach.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/attach.unit.test.ts @@ -565,5 +565,25 @@ getInfoPerOS().forEach(([osName, osType, path]) => { expect(debugConfig).to.have.property('justMyCode', testParams.expectedResult); }); }); + + test('Send consoleName value to debugpy as consoleTitle', async () => { + const activeFile = 'xyz.py'; + const consoleName = "My Console Name"; + 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[]; + + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...attach, + debugOptions, + consoleName, + }); + expect(debugConfig).to.have.property('consoleTitle', consoleName); + }); }); }); diff --git a/src/test/unittest/configuration/resolvers/launch.unit.test.ts b/src/test/unittest/configuration/resolvers/launch.unit.test.ts index 9039781e..291e6fa1 100644 --- a/src/test/unittest/configuration/resolvers/launch.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/launch.unit.test.ts @@ -953,6 +953,21 @@ getInfoPerOS().forEach(([osName, osType, path]) => { }); }); + test('Send consoleName value to debugpy as consoleTitle', async () => { + const consoleName = "My Console Name"; + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const debugConfig = await resolveDebugConfiguration(workspaceFolder, { + ...launch, + ...{ consoleName } + }); + expect(debugConfig).to.have.property('consoleTitle', consoleName); + }); + async function testSetting( requestType: 'launch' | 'attach', settings: Record, From dd8528d55c2a40dfcd6077fe9f4de6e70348a215 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Fri, 19 Apr 2024 16:08:30 -0700 Subject: [PATCH 4/5] Fix lint --- src/test/unittest/configuration/resolvers/attach.unit.test.ts | 2 +- src/test/unittest/configuration/resolvers/launch.unit.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/unittest/configuration/resolvers/attach.unit.test.ts b/src/test/unittest/configuration/resolvers/attach.unit.test.ts index e9353acc..2994e751 100644 --- a/src/test/unittest/configuration/resolvers/attach.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/attach.unit.test.ts @@ -568,7 +568,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { test('Send consoleName value to debugpy as consoleTitle', async () => { const activeFile = 'xyz.py'; - const consoleName = "My Console Name"; + const consoleName = 'My Console Name'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); diff --git a/src/test/unittest/configuration/resolvers/launch.unit.test.ts b/src/test/unittest/configuration/resolvers/launch.unit.test.ts index 291e6fa1..29c2c313 100644 --- a/src/test/unittest/configuration/resolvers/launch.unit.test.ts +++ b/src/test/unittest/configuration/resolvers/launch.unit.test.ts @@ -954,7 +954,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { }); test('Send consoleName value to debugpy as consoleTitle', async () => { - const consoleName = "My Console Name"; + const consoleName = 'My Console Name'; const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; @@ -963,7 +963,7 @@ getInfoPerOS().forEach(([osName, osType, path]) => { const debugConfig = await resolveDebugConfiguration(workspaceFolder, { ...launch, - ...{ consoleName } + ...{ consoleName }, }); expect(debugConfig).to.have.property('consoleTitle', consoleName); }); From 38f6e81fb225abb721ea932dc96dd2cbd5534830 Mon Sep 17 00:00:00 2001 From: Paula Date: Fri, 19 Apr 2024 15:16:46 -0700 Subject: [PATCH 5/5] Show variables in hex (#317) * Add debug Visualizer * Add proposal * add hex view * fux lint * fix lint * Add localization, move tree creation to own file * fix compile errors --- package.json | 11 +- src/extension/common/utils/localize.ts | 4 + .../debugger/visualizers/inlineHexDecoder.ts | 34 ++++ src/extension/extensionInit.ts | 33 +++- vscode.proposed.debugVisualization.d.ts | 170 ++++++++++++++++++ 5 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 src/extension/debugger/visualizers/inlineHexDecoder.ts create mode 100644 vscode.proposed.debugVisualization.d.ts diff --git a/package.json b/package.json index 4bb21344..0794887c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "publisher": "ms-python", "enabledApiProposals": [ "portsAttributes", - "contribIssueReporter" + "contribIssueReporter", + "debugVisualization" ], "license": "MIT", "homepage": "https://github.com/Microsoft/vscode-python-debugger", @@ -514,7 +515,13 @@ }, "when": "!virtualWorkspace && shellExecutionSupported" } - ] + ], + "debugVisualizers": [ + { + "id": "inlineHexDecoder", + "when": "debugConfigurationType == 'debugpy' && (variableType == 'float' || variableType == 'int')" + } + ] }, "extensionDependencies": [ "ms-python.python" diff --git a/src/extension/common/utils/localize.ts b/src/extension/common/utils/localize.ts index c81182c1..8636c962 100644 --- a/src/extension/common/utils/localize.ts +++ b/src/extension/common/utils/localize.ts @@ -177,3 +177,7 @@ export namespace pickArgsInput { export const title = l10n.t('Command Line Arguments'); export const prompt = l10n.t('Enter the command line arguments you want to pass to the program'); } + +export namespace DebugVisualizers { + export const hexDecoder = l10n.t('Show as Hex'); +} diff --git a/src/extension/debugger/visualizers/inlineHexDecoder.ts b/src/extension/debugger/visualizers/inlineHexDecoder.ts new file mode 100644 index 00000000..630fbb5d --- /dev/null +++ b/src/extension/debugger/visualizers/inlineHexDecoder.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { DebugVisualizationContext } from 'vscode'; + +export function registerHexDebugVisualizationTreeProvider() { + return { + getTreeItem(context: DebugVisualizationContext) { + const decoded = `0x${Number(context.variable.value).toString(16)}`; + return { + label: context.variable.name.toString(), + description: decoded.toString(), + buffer: decoded, + canEdit: true, + context, + }; + }, + getChildren(_element: any) { + return undefined; + }, + editItem(item: any, value: string) { + item.buffer = `0x${Number(value).toString(16)}`; + item.description = item.buffer.toString(); + + item.context.session.customRequest('setExpression', { + expression: item.context.variable.evaluateName, + frameId: item.context.frameId, + value: JSON.stringify(item.buffer.toString()), + }); + + return item; + }, + }; +} diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index 904621c0..deb662b4 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -3,7 +3,18 @@ 'use strict'; -import { debug, DebugConfigurationProviderTriggerKind, languages, Uri, window, workspace } from 'vscode'; +import { + debug, + DebugConfigurationProviderTriggerKind, + DebugTreeItem, + DebugVisualization, + DebugVisualizationContext, + languages, + ThemeIcon, + Uri, + window, + workspace, +} from 'vscode'; import { executeCommand, getConfiguration, registerCommand, startDebugging } from './common/vscodeapi'; import { DebuggerTypeName } from './constants'; import { DynamicPythonDebugConfigurationService } from './debugger/configuration/dynamicdebugConfigurationService'; @@ -30,13 +41,14 @@ import { DebugSessionTelemetry } from './common/application/debugSessionTelemetr import { JsonLanguages, LaunchJsonCompletionProvider } from './debugger/configuration/launch.json/completionProvider'; import { LaunchJsonUpdaterServiceHelper } from './debugger/configuration/launch.json/updaterServiceHelper'; import { ignoreErrors } from './common/promiseUtils'; -import { pickArgsInput } from './common/utils/localize'; +import { DebugVisualizers, pickArgsInput } from './common/utils/localize'; import { DebugPortAttributesProvider } from './debugger/debugPort/portAttributesProvider'; import { getConfigurationsByUri } from './debugger/configuration/launch.json/launchJsonReader'; import { DebugpySocketsHandler } from './debugger/hooks/debugpySocketsHandler'; import { openReportIssue } from './common/application/commands/reportIssueCommand'; import { buildApi } from './api'; import { IExtensionApi } from './apiTypes'; +import { registerHexDebugVisualizationTreeProvider } from './debugger/visualizers/inlineHexDecoder'; export async function registerDebugger(context: IExtensionContext): Promise { const childProcessAttachService = new ChildProcessAttachService(); @@ -177,5 +189,22 @@ export async function registerDebugger(context: IExtensionContext): Promise('inlineHexDecoder', registerHexDebugVisualizationTreeProvider()), + ); + + context.subscriptions.push( + debug.registerDebugVisualizationProvider('inlineHexDecoder', { + provideDebugVisualization(_context, _token) { + const v = new DebugVisualization(DebugVisualizers.hexDecoder); + v.iconPath = new ThemeIcon('eye'); + v.visualization = { treeId: 'inlineHexDecoder' }; + return [v]; + }, + }), + ); + return buildApi(); } diff --git a/vscode.proposed.debugVisualization.d.ts b/vscode.proposed.debugVisualization.d.ts new file mode 100644 index 00000000..cb12eaa0 --- /dev/null +++ b/vscode.proposed.debugVisualization.d.ts @@ -0,0 +1,170 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +declare module 'vscode' { + export namespace debug { + /** + * Registers a custom data visualization for variables when debugging. + * + * @param id The corresponding ID in the package.json `debugVisualizers` contribution point. + * @param provider The {@link DebugVisualizationProvider} to register + */ + export function registerDebugVisualizationProvider( + id: string, + provider: DebugVisualizationProvider + ): Disposable; + + /** + * Registers a tree that can be referenced by {@link DebugVisualization.visualization}. + * @param id + * @param provider + */ + export function registerDebugVisualizationTreeProvider( + id: string, + provider: DebugVisualizationTree + ): Disposable; + } + + /** + * An item from the {@link DebugVisualizationTree} + */ + export interface DebugTreeItem { + /** + * A human-readable string describing this item. + */ + label: string; + + /** + * A human-readable string which is rendered less prominent. + */ + description?: string; + + /** + * {@link TreeItemCollapsibleState} of the tree item. + */ + collapsibleState?: TreeItemCollapsibleState; + + /** + * Context value of the tree item. This can be used to contribute item specific actions in the tree. + * For example, a tree item is given a context value as `folder`. When contributing actions to `view/item/context` + * using `menus` extension point, you can specify context value for key `viewItem` in `when` expression like `viewItem == folder`. + * ```json + * "contributes": { + * "menus": { + * "view/item/context": [ + * { + * "command": "extension.deleteFolder", + * "when": "viewItem == folder" + * } + * ] + * } + * } + * ``` + * This will show action `extension.deleteFolder` only for items with `contextValue` is `folder`. + */ + contextValue?: string; + + /** + * Whether this item can be edited by the user. + */ + canEdit?: boolean; + } + + /** + * Provides a tree that can be referenced in debug visualizations. + */ + export interface DebugVisualizationTree { + /** + * Gets the tree item for an element or the base context item. + */ + getTreeItem(context: DebugVisualizationContext): ProviderResult; + /** + * Gets children for the tree item or the best context item. + */ + getChildren(element: T): ProviderResult; + /** + * Handles the user editing an item. + */ + editItem?(item: T, value: string): ProviderResult; + } + + export class DebugVisualization { + /** + * The name of the visualization to show to the user. + */ + name: string; + + /** + * An icon for the view when it's show in inline actions. + */ + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + + /** + * Visualization to use for the variable. This may be either: + * - A command to run when the visualization is selected for a variable. + * - A reference to a previously-registered {@link DebugVisualizationTree} + */ + visualization?: Command | { treeId: string }; + + /** + * Creates a new debug visualization object. + * @param name Name of the visualization to show to the user. + */ + constructor(name: string); + } + + export interface DebugVisualizationProvider { + /** + * Called for each variable when the debug session stops. It should return + * any visualizations the extension wishes to show to the user. + * + * Note that this is only called when its `when` clause defined under the + * `debugVisualizers` contribution point in the `package.json` evaluates + * to true. + */ + provideDebugVisualization(context: DebugVisualizationContext, token: CancellationToken): ProviderResult; + + /** + * Invoked for a variable when a user picks the visualizer. + * + * It may return a {@link TreeView} that's shown in the Debug Console or + * inline in a hover. A visualizer may choose to return `undefined` from + * this function and instead trigger other actions in the UI, such as opening + * a custom {@link WebviewView}. + */ + resolveDebugVisualization?(visualization: T, token: CancellationToken): ProviderResult; + } + + export interface DebugVisualizationContext { + /** + * The Debug Adapter Protocol Variable to be visualized. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable + */ + variable: any; + /** + * The Debug Adapter Protocol variable reference the type (such as a scope + * or another variable) that contained this one. Empty for variables + * that came from user evaluations in the Debug Console. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable + */ + containerId?: number; + /** + * The ID of the Debug Adapter Protocol StackFrame in which the variable was found, + * for variables that came from scopes in a stack frame. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame + */ + frameId?: number; + /** + * The ID of the Debug Adapter Protocol Thread in which the variable was found. + * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame + */ + threadId: number; + /** + * The debug session the variable belongs to. + */ + session: DebugSession; + } +}