Skip to content

Commit 0f507f3

Browse files
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
1 parent ef9e981 commit 0f507f3

File tree

5 files changed

+248
-4
lines changed

5 files changed

+248
-4
lines changed

package.json

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"publisher": "ms-python",
77
"enabledApiProposals": [
88
"portsAttributes",
9-
"contribIssueReporter"
9+
"contribIssueReporter",
10+
"debugVisualization"
1011
],
1112
"license": "MIT",
1213
"homepage": "https://github.com/Microsoft/vscode-python-debugger",
@@ -508,7 +509,13 @@
508509
},
509510
"when": "!virtualWorkspace && shellExecutionSupported"
510511
}
511-
]
512+
],
513+
"debugVisualizers": [
514+
{
515+
"id": "inlineHexDecoder",
516+
"when": "debugConfigurationType == 'debugpy' && (variableType == 'float' || variableType == 'int')"
517+
}
518+
]
512519
},
513520
"extensionDependencies": [
514521
"ms-python.python"

src/extension/common/utils/localize.ts

+4
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,7 @@ export namespace pickArgsInput {
177177
export const title = l10n.t('Command Line Arguments');
178178
export const prompt = l10n.t('Enter the command line arguments you want to pass to the program');
179179
}
180+
181+
export namespace DebugVisualizers {
182+
export const hexDecoder = l10n.t('Show as Hex');
183+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { DebugVisualizationContext } from 'vscode';
5+
6+
export function registerHexDebugVisualizationTreeProvider() {
7+
return {
8+
getTreeItem(context: DebugVisualizationContext) {
9+
const decoded = `0x${Number(context.variable.value).toString(16)}`;
10+
return {
11+
label: context.variable.name.toString(),
12+
description: decoded.toString(),
13+
buffer: decoded,
14+
canEdit: true,
15+
context,
16+
};
17+
},
18+
getChildren(_element: any) {
19+
return undefined;
20+
},
21+
editItem(item: any, value: string) {
22+
item.buffer = `0x${Number(value).toString(16)}`;
23+
item.description = item.buffer.toString();
24+
25+
item.context.session.customRequest('setExpression', {
26+
expression: item.context.variable.evaluateName,
27+
frameId: item.context.frameId,
28+
value: JSON.stringify(item.buffer.toString()),
29+
});
30+
31+
return item;
32+
},
33+
};
34+
}

src/extension/extensionInit.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@
33

44
'use strict';
55

6-
import { debug, DebugConfigurationProviderTriggerKind, languages, Uri, window, workspace } from 'vscode';
6+
import {
7+
debug,
8+
DebugConfigurationProviderTriggerKind,
9+
DebugTreeItem,
10+
DebugVisualization,
11+
DebugVisualizationContext,
12+
languages,
13+
ThemeIcon,
14+
Uri,
15+
window,
16+
workspace,
17+
} from 'vscode';
718
import { executeCommand, getConfiguration, registerCommand, startDebugging } from './common/vscodeapi';
819
import { DebuggerTypeName } from './constants';
920
import { DynamicPythonDebugConfigurationService } from './debugger/configuration/dynamicdebugConfigurationService';
@@ -30,13 +41,14 @@ import { DebugSessionTelemetry } from './common/application/debugSessionTelemetr
3041
import { JsonLanguages, LaunchJsonCompletionProvider } from './debugger/configuration/launch.json/completionProvider';
3142
import { LaunchJsonUpdaterServiceHelper } from './debugger/configuration/launch.json/updaterServiceHelper';
3243
import { ignoreErrors } from './common/promiseUtils';
33-
import { pickArgsInput } from './common/utils/localize';
44+
import { DebugVisualizers, pickArgsInput } from './common/utils/localize';
3445
import { DebugPortAttributesProvider } from './debugger/debugPort/portAttributesProvider';
3546
import { getConfigurationsByUri } from './debugger/configuration/launch.json/launchJsonReader';
3647
import { DebugpySocketsHandler } from './debugger/hooks/debugpySocketsHandler';
3748
import { openReportIssue } from './common/application/commands/reportIssueCommand';
3849
import { buildApi } from './api';
3950
import { IExtensionApi } from './apiTypes';
51+
import { registerHexDebugVisualizationTreeProvider } from './debugger/visualizers/inlineHexDecoder';
4052

4153
export async function registerDebugger(context: IExtensionContext): Promise<IExtensionApi> {
4254
const childProcessAttachService = new ChildProcessAttachService();
@@ -177,5 +189,22 @@ export async function registerDebugger(context: IExtensionContext): Promise<IExt
177189
}),
178190
);
179191

192+
context.subscriptions.push(
193+
debug.registerDebugVisualizationTreeProvider<
194+
DebugTreeItem & { byte?: number; buffer: String; context: DebugVisualizationContext }
195+
>('inlineHexDecoder', registerHexDebugVisualizationTreeProvider()),
196+
);
197+
198+
context.subscriptions.push(
199+
debug.registerDebugVisualizationProvider('inlineHexDecoder', {
200+
provideDebugVisualization(_context, _token) {
201+
const v = new DebugVisualization(DebugVisualizers.hexDecoder);
202+
v.iconPath = new ThemeIcon('eye');
203+
v.visualization = { treeId: 'inlineHexDecoder' };
204+
return [v];
205+
},
206+
}),
207+
);
208+
180209
return buildApi();
181210
}
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
7+
declare module 'vscode' {
8+
export namespace debug {
9+
/**
10+
* Registers a custom data visualization for variables when debugging.
11+
*
12+
* @param id The corresponding ID in the package.json `debugVisualizers` contribution point.
13+
* @param provider The {@link DebugVisualizationProvider} to register
14+
*/
15+
export function registerDebugVisualizationProvider<T extends DebugVisualization>(
16+
id: string,
17+
provider: DebugVisualizationProvider<T>
18+
): Disposable;
19+
20+
/**
21+
* Registers a tree that can be referenced by {@link DebugVisualization.visualization}.
22+
* @param id
23+
* @param provider
24+
*/
25+
export function registerDebugVisualizationTreeProvider<T extends DebugTreeItem>(
26+
id: string,
27+
provider: DebugVisualizationTree<T>
28+
): Disposable;
29+
}
30+
31+
/**
32+
* An item from the {@link DebugVisualizationTree}
33+
*/
34+
export interface DebugTreeItem {
35+
/**
36+
* A human-readable string describing this item.
37+
*/
38+
label: string;
39+
40+
/**
41+
* A human-readable string which is rendered less prominent.
42+
*/
43+
description?: string;
44+
45+
/**
46+
* {@link TreeItemCollapsibleState} of the tree item.
47+
*/
48+
collapsibleState?: TreeItemCollapsibleState;
49+
50+
/**
51+
* Context value of the tree item. This can be used to contribute item specific actions in the tree.
52+
* For example, a tree item is given a context value as `folder`. When contributing actions to `view/item/context`
53+
* using `menus` extension point, you can specify context value for key `viewItem` in `when` expression like `viewItem == folder`.
54+
* ```json
55+
* "contributes": {
56+
* "menus": {
57+
* "view/item/context": [
58+
* {
59+
* "command": "extension.deleteFolder",
60+
* "when": "viewItem == folder"
61+
* }
62+
* ]
63+
* }
64+
* }
65+
* ```
66+
* This will show action `extension.deleteFolder` only for items with `contextValue` is `folder`.
67+
*/
68+
contextValue?: string;
69+
70+
/**
71+
* Whether this item can be edited by the user.
72+
*/
73+
canEdit?: boolean;
74+
}
75+
76+
/**
77+
* Provides a tree that can be referenced in debug visualizations.
78+
*/
79+
export interface DebugVisualizationTree<T extends DebugTreeItem = DebugTreeItem> {
80+
/**
81+
* Gets the tree item for an element or the base context item.
82+
*/
83+
getTreeItem(context: DebugVisualizationContext): ProviderResult<T>;
84+
/**
85+
* Gets children for the tree item or the best context item.
86+
*/
87+
getChildren(element: T): ProviderResult<T[]>;
88+
/**
89+
* Handles the user editing an item.
90+
*/
91+
editItem?(item: T, value: string): ProviderResult<T>;
92+
}
93+
94+
export class DebugVisualization {
95+
/**
96+
* The name of the visualization to show to the user.
97+
*/
98+
name: string;
99+
100+
/**
101+
* An icon for the view when it's show in inline actions.
102+
*/
103+
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
104+
105+
/**
106+
* Visualization to use for the variable. This may be either:
107+
* - A command to run when the visualization is selected for a variable.
108+
* - A reference to a previously-registered {@link DebugVisualizationTree}
109+
*/
110+
visualization?: Command | { treeId: string };
111+
112+
/**
113+
* Creates a new debug visualization object.
114+
* @param name Name of the visualization to show to the user.
115+
*/
116+
constructor(name: string);
117+
}
118+
119+
export interface DebugVisualizationProvider<T extends DebugVisualization = DebugVisualization> {
120+
/**
121+
* Called for each variable when the debug session stops. It should return
122+
* any visualizations the extension wishes to show to the user.
123+
*
124+
* Note that this is only called when its `when` clause defined under the
125+
* `debugVisualizers` contribution point in the `package.json` evaluates
126+
* to true.
127+
*/
128+
provideDebugVisualization(context: DebugVisualizationContext, token: CancellationToken): ProviderResult<T[]>;
129+
130+
/**
131+
* Invoked for a variable when a user picks the visualizer.
132+
*
133+
* It may return a {@link TreeView} that's shown in the Debug Console or
134+
* inline in a hover. A visualizer may choose to return `undefined` from
135+
* this function and instead trigger other actions in the UI, such as opening
136+
* a custom {@link WebviewView}.
137+
*/
138+
resolveDebugVisualization?(visualization: T, token: CancellationToken): ProviderResult<T>;
139+
}
140+
141+
export interface DebugVisualizationContext {
142+
/**
143+
* The Debug Adapter Protocol Variable to be visualized.
144+
* @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable
145+
*/
146+
variable: any;
147+
/**
148+
* The Debug Adapter Protocol variable reference the type (such as a scope
149+
* or another variable) that contained this one. Empty for variables
150+
* that came from user evaluations in the Debug Console.
151+
* @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable
152+
*/
153+
containerId?: number;
154+
/**
155+
* The ID of the Debug Adapter Protocol StackFrame in which the variable was found,
156+
* for variables that came from scopes in a stack frame.
157+
* @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame
158+
*/
159+
frameId?: number;
160+
/**
161+
* The ID of the Debug Adapter Protocol Thread in which the variable was found.
162+
* @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_StackFrame
163+
*/
164+
threadId: number;
165+
/**
166+
* The debug session the variable belongs to.
167+
*/
168+
session: DebugSession;
169+
}
170+
}

0 commit comments

Comments
 (0)