diff --git a/package.json b/package.json index 3fc1c3e6..d0c4c484 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", @@ -508,7 +509,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; + } +}