Skip to content

Commit

Permalink
Show variables in hex (#317)
Browse files Browse the repository at this point in the history
* Add debug Visualizer

* Add proposal

* add hex view

* fux lint

* fix lint

* Add localization, move tree creation to own file

* fix compile errors
  • Loading branch information
paulacamargo25 authored Apr 19, 2024
1 parent ef9e981 commit 0f507f3
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 4 deletions.
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"publisher": "ms-python",
"enabledApiProposals": [
"portsAttributes",
"contribIssueReporter"
"contribIssueReporter",
"debugVisualization"
],
"license": "MIT",
"homepage": "https://github.com/Microsoft/vscode-python-debugger",
Expand Down Expand Up @@ -508,7 +509,13 @@
},
"when": "!virtualWorkspace && shellExecutionSupported"
}
]
],
"debugVisualizers": [
{
"id": "inlineHexDecoder",
"when": "debugConfigurationType == 'debugpy' && (variableType == 'float' || variableType == 'int')"
}
]
},
"extensionDependencies": [
"ms-python.python"
Expand Down
4 changes: 4 additions & 0 deletions src/extension/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
34 changes: 34 additions & 0 deletions src/extension/debugger/visualizers/inlineHexDecoder.ts
Original file line number Diff line number Diff line change
@@ -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;
},
};
}
33 changes: 31 additions & 2 deletions src/extension/extensionInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<IExtensionApi> {
const childProcessAttachService = new ChildProcessAttachService();
Expand Down Expand Up @@ -177,5 +189,22 @@ export async function registerDebugger(context: IExtensionContext): Promise<IExt
}),
);

context.subscriptions.push(
debug.registerDebugVisualizationTreeProvider<
DebugTreeItem & { byte?: number; buffer: String; context: DebugVisualizationContext }
>('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();
}
170 changes: 170 additions & 0 deletions vscode.proposed.debugVisualization.d.ts
Original file line number Diff line number Diff line change
@@ -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<T extends DebugVisualization>(
id: string,
provider: DebugVisualizationProvider<T>
): Disposable;

/**
* Registers a tree that can be referenced by {@link DebugVisualization.visualization}.
* @param id
* @param provider
*/
export function registerDebugVisualizationTreeProvider<T extends DebugTreeItem>(
id: string,
provider: DebugVisualizationTree<T>
): 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<T extends DebugTreeItem = DebugTreeItem> {
/**
* Gets the tree item for an element or the base context item.
*/
getTreeItem(context: DebugVisualizationContext): ProviderResult<T>;
/**
* Gets children for the tree item or the best context item.
*/
getChildren(element: T): ProviderResult<T[]>;
/**
* Handles the user editing an item.
*/
editItem?(item: T, value: string): ProviderResult<T>;
}

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<T extends DebugVisualization = DebugVisualization> {
/**
* 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<T[]>;

/**
* 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<T>;
}

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;
}
}

0 comments on commit 0f507f3

Please sign in to comment.