Path: blob/main/src/vs/workbench/contrib/debug/browser/variablesView.ts
5240 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../base/browser/dom.js';6import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';7import { HighlightedLabel, IHighlight } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';8import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';9import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';10import { AsyncDataTree, IAsyncDataTreeViewState } from '../../../../base/browser/ui/tree/asyncDataTree.js';11import { ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from '../../../../base/browser/ui/tree/tree.js';12import { IAction, toAction } from '../../../../base/common/actions.js';13import { coalesce } from '../../../../base/common/arrays.js';14import { RunOnceScheduler } from '../../../../base/common/async.js';15import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';16import { Codicon } from '../../../../base/common/codicons.js';17import { FuzzyScore, createMatches } from '../../../../base/common/filters.js';18import { IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';19import { ThemeIcon } from '../../../../base/common/themables.js';20import { localize } from '../../../../nls.js';21import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';22import { IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';23import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';24import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';25import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';26import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';27import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';28import { IHoverService } from '../../../../platform/hover/browser/hover.js';29import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';30import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';31import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js';32import { INotificationService } from '../../../../platform/notification/common/notification.js';33import { IOpenerService } from '../../../../platform/opener/common/opener.js';34import { ProgressLocation } from '../../../../platform/progress/common/progress.js';35import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';36import { IThemeService } from '../../../../platform/theme/common/themeService.js';37import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js';38import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';39import { IViewDescriptorService } from '../../../common/views.js';40import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';41import { IExtensionService } from '../../../services/extensions/common/extensions.js';42import { IViewsService } from '../../../services/views/common/viewsService.js';43import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';44import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDebugService, IDebugViewWithVariables, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID, WATCH_VIEW_ID } from '../common/debug.js';45import { getContextForVariable } from '../common/debugContext.js';46import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from '../common/debugModel.js';47import { DebugVisualizer, IDebugVisualizerService } from '../common/debugVisualizers.js';48import { AbstractExpressionDataSource, AbstractExpressionsRenderer, expressionAndScopeLabelProvider, IExpressionTemplateData, IInputBoxOptions, renderViewTree } from './baseDebugView.js';49import { ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_VALUE_ID, COPY_VALUE_LABEL, setDataBreakpointInfoResponse } from './debugCommands.js';50import { DebugExpressionRenderer } from './debugExpressionRenderer.js';5152const $ = dom.$;53let forgetScopes = true;5455let variableInternalContext: Variable | undefined;5657interface IVariablesContext {58sessionId: string | undefined;59container: DebugProtocol.Variable | DebugProtocol.Scope | DebugProtocol.EvaluateArguments;60variable: DebugProtocol.Variable;61}6263export class VariablesView extends ViewPane implements IDebugViewWithVariables {6465private updateTreeScheduler: RunOnceScheduler;66private needsRefresh = false;67private tree!: WorkbenchAsyncDataTree<IStackFrame | null, IExpression | IScope, FuzzyScore>;68private savedViewState = new Map<string, IAsyncDataTreeViewState>();69private autoExpandedScopes = new Set<string>();7071public get treeSelection() {72return this.tree.getSelection();73}7475constructor(76options: IViewletViewOptions,77@IContextMenuService contextMenuService: IContextMenuService,78@IDebugService private readonly debugService: IDebugService,79@IKeybindingService keybindingService: IKeybindingService,80@IConfigurationService configurationService: IConfigurationService,81@IInstantiationService instantiationService: IInstantiationService,82@IViewDescriptorService viewDescriptorService: IViewDescriptorService,83@IContextKeyService contextKeyService: IContextKeyService,84@IOpenerService openerService: IOpenerService,85@IThemeService themeService: IThemeService,86@IHoverService hoverService: IHoverService,87@IMenuService private readonly menuService: IMenuService88) {89super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);9091// Use scheduler to prevent unnecessary flashing92this.updateTreeScheduler = new RunOnceScheduler(async () => {93const stackFrame = this.debugService.getViewModel().focusedStackFrame;9495this.needsRefresh = false;96const input = this.tree.getInput();97if (input) {98this.savedViewState.set(input.getId(), this.tree.getViewState());99}100if (!stackFrame) {101await this.tree.setInput(null);102return;103}104105const viewState = this.savedViewState.get(stackFrame.getId());106await this.tree.setInput(stackFrame, viewState);107108// Automatically expand the first non-expensive scope109const scopes = await stackFrame.getScopes();110const toExpand = scopes.find(s => !s.expensive);111112// A race condition could be present causing the scopes here to be different from the scopes that the tree just retrieved.113// If that happened, don't try to reveal anything, it will be straightened out on the next update114if (toExpand && this.tree.hasNode(toExpand)) {115this.autoExpandedScopes.add(toExpand.getId());116await this.tree.expand(toExpand);117}118}, 400);119}120121protected override renderBody(container: HTMLElement): void {122super.renderBody(container);123124this.element.classList.add('debug-pane');125container.classList.add('debug-variables');126const treeContainer = renderViewTree(container);127const expressionRenderer = this.instantiationService.createInstance(DebugExpressionRenderer);128this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree<IStackFrame | null, IExpression | IScope, FuzzyScore>, 'VariablesView', treeContainer, new VariablesDelegate(),129[130this.instantiationService.createInstance(VariablesRenderer, expressionRenderer),131this.instantiationService.createInstance(VisualizedVariableRenderer, expressionRenderer),132new ScopesRenderer(),133new ScopeErrorRenderer(),134],135this.instantiationService.createInstance(VariablesDataSource), {136accessibilityProvider: new VariablesAccessibilityProvider(),137identityProvider: { getId: (element: IExpression | IScope) => element.getId() },138keyboardNavigationLabelProvider: expressionAndScopeLabelProvider,139overrideStyles: this.getLocationBasedColors().listOverrideStyles140});141142this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree));143this.tree.setInput(this.debugService.getViewModel().focusedStackFrame ?? null);144145CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);146147this._register(this.debugService.getViewModel().onDidFocusStackFrame(sf => {148if (!this.isBodyVisible()) {149this.needsRefresh = true;150return;151}152153// Refresh the tree immediately if the user explictly changed stack frames.154// Otherwise postpone the refresh until user stops stepping.155const timeout = sf.explicit ? 0 : undefined;156this.updateTreeScheduler.schedule(timeout);157}));158this._register(this.debugService.getViewModel().onWillUpdateViews(() => {159const stackFrame = this.debugService.getViewModel().focusedStackFrame;160if (stackFrame && forgetScopes) {161stackFrame.forgetScopes();162}163forgetScopes = true;164this.tree.updateChildren();165}));166this._register(this.tree);167this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));168this._register(this.tree.onContextMenu(async e => await this.onContextMenu(e)));169170this._register(this.onDidChangeBodyVisibility(visible => {171if (visible && this.needsRefresh) {172this.updateTreeScheduler.schedule();173}174}));175let horizontalScrolling: boolean | undefined;176this._register(this.debugService.getViewModel().onDidSelectExpression(e => {177const variable = e?.expression;178if (variable && this.tree.hasNode(variable)) {179horizontalScrolling = this.tree.options.horizontalScrolling;180if (horizontalScrolling) {181this.tree.updateOptions({ horizontalScrolling: false });182}183184this.tree.rerender(variable);185} else if (!e && horizontalScrolling !== undefined) {186this.tree.updateOptions({ horizontalScrolling: horizontalScrolling });187horizontalScrolling = undefined;188}189}));190this._register(this.debugService.getViewModel().onDidEvaluateLazyExpression(async e => {191if (e instanceof Variable && this.tree.hasNode(e)) {192await this.tree.updateChildren(e, false, true);193await this.tree.expand(e);194}195}));196this._register(this.debugService.onDidEndSession(() => {197this.savedViewState.clear();198this.autoExpandedScopes.clear();199}));200}201202protected override layoutBody(width: number, height: number): void {203super.layoutBody(height, width);204this.tree.layout(width, height);205}206207override focus(): void {208super.focus();209this.tree.domFocus();210}211212collapseAll(): void {213this.tree.collapseAll();214}215216private onMouseDblClick(e: ITreeMouseEvent<IExpression | IScope>): void {217if (this.canSetExpressionValue(e.element)) {218this.debugService.getViewModel().setSelectedExpression(e.element, false);219}220}221222private canSetExpressionValue(e: IExpression | IScope | null): e is IExpression {223const session = this.debugService.getViewModel().focusedSession;224if (!session) {225return false;226}227228if (e instanceof VisualizedExpression) {229return !!e.treeItem.canEdit;230}231232if (!session.capabilities?.supportsSetVariable && !session.capabilities?.supportsSetExpression) {233return false;234}235236return e instanceof Variable && !e.presentationHint?.attributes?.includes('readOnly') && !e.presentationHint?.lazy;237}238239private async onContextMenu(e: ITreeContextMenuEvent<IExpression | IScope>): Promise<void> {240const element = e.element;241242// Handle scope context menu243if (element instanceof Scope) {244return this.openContextMenuForScope(e, element);245}246247// Handle variable context menu248if (!(element instanceof Variable) || !element.value) {249return;250}251252return openContextMenuForVariableTreeElement(this.contextKeyService, this.menuService, this.contextMenuService, MenuId.DebugVariablesContext, e);253}254255private openContextMenuForScope(e: ITreeContextMenuEvent<IExpression | IScope>, scope: Scope): void {256const context = { scope: { name: scope.name } };257const menu = this.menuService.getMenuActions(MenuId.DebugScopesContext, this.contextKeyService, { arg: context, shouldForwardArgs: false });258const { secondary } = getContextMenuActions(menu, 'inline');259260this.contextMenuService.showContextMenu({261getAnchor: () => e.anchor,262getActions: () => secondary263});264}265}266267export async function openContextMenuForVariableTreeElement(parentContextKeyService: IContextKeyService, menuService: IMenuService, contextMenuService: IContextMenuService, menuId: MenuId, e: ITreeContextMenuEvent<IExpression | IScope>) {268const variable = e.element;269if (!(variable instanceof Variable) || !variable.value) {270return;271}272273const contextKeyService = await getContextForVariableMenuWithDataAccess(parentContextKeyService, variable);274const context: IVariablesContext = getVariablesContext(variable);275const menu = menuService.getMenuActions(menuId, contextKeyService, { arg: context, shouldForwardArgs: false });276277const { secondary } = getContextMenuActions(menu, 'inline');278contextMenuService.showContextMenu({279getAnchor: () => e.anchor,280getActions: () => secondary281});282}283284const getVariablesContext = (variable: Variable): IVariablesContext => ({285sessionId: variable.getSession()?.getId(),286container: variable.parent instanceof Expression287? { expression: variable.parent.name }288: (variable.parent as (Variable | Scope)).toDebugProtocolObject(),289variable: variable.toDebugProtocolObject()290});291292/**293* Gets a context key overlay that has context for the given variable, including data access info.294*/295async function getContextForVariableMenuWithDataAccess(parentContext: IContextKeyService, variable: Variable) {296const session = variable.getSession();297if (!session || !session.capabilities.supportsDataBreakpoints) {298return getContextForVariableMenuBase(parentContext, variable);299}300301const contextKeys: [string, unknown][] = [];302const dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference);303const dataBreakpointId = dataBreakpointInfoResponse?.dataId;304const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes;305setDataBreakpointInfoResponse(dataBreakpointInfoResponse);306307if (!dataBreakpointAccessTypes) {308contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);309} else {310for (const accessType of dataBreakpointAccessTypes) {311switch (accessType) {312case 'read':313contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, !!dataBreakpointId]);314break;315case 'write':316contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);317break;318case 'readWrite':319contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, !!dataBreakpointId]);320break;321}322}323}324325return getContextForVariableMenuBase(parentContext, variable, contextKeys);326}327328/**329* Gets a context key overlay that has context for the given variable.330*/331function getContextForVariableMenuBase(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) {332variableInternalContext = variable;333return getContextForVariable(parentContext, variable, additionalContext);334}335336function isStackFrame(obj: any): obj is IStackFrame {337return obj instanceof StackFrame;338}339340class VariablesDataSource extends AbstractExpressionDataSource<IStackFrame | null, IExpression | IScope> {341342public override hasChildren(element: IStackFrame | null | IExpression | IScope): boolean {343if (!element) {344return false;345}346if (isStackFrame(element)) {347return true;348}349350return element.hasChildren;351}352353protected override doGetChildren(element: IStackFrame | IExpression | IScope): Promise<(IExpression | IScope)[]> {354if (isStackFrame(element)) {355return element.getScopes();356}357358return element.getChildren();359}360}361362interface IScopeTemplateData {363name: HTMLElement;364label: HighlightedLabel;365}366367class VariablesDelegate implements IListVirtualDelegate<IExpression | IScope> {368369getHeight(element: IExpression | IScope): number {370return 22;371}372373getTemplateId(element: IExpression | IScope): string {374if (element instanceof ErrorScope) {375return ScopeErrorRenderer.ID;376}377378if (element instanceof Scope) {379return ScopesRenderer.ID;380}381382if (element instanceof VisualizedExpression) {383return VisualizedVariableRenderer.ID;384}385386return VariablesRenderer.ID;387}388}389390class ScopesRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeTemplateData> {391392static readonly ID = 'scope';393394get templateId(): string {395return ScopesRenderer.ID;396}397398renderTemplate(container: HTMLElement): IScopeTemplateData {399const name = dom.append(container, $('.scope'));400const label = new HighlightedLabel(name);401402return { name, label };403}404405renderElement(element: ITreeNode<IScope, FuzzyScore>, index: number, templateData: IScopeTemplateData): void {406templateData.label.set(element.element.name, createMatches(element.filterData));407}408409disposeTemplate(templateData: IScopeTemplateData): void {410templateData.label.dispose();411}412}413414interface IScopeErrorTemplateData {415error: HTMLElement;416}417418class ScopeErrorRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeErrorTemplateData> {419420static readonly ID = 'scopeError';421422get templateId(): string {423return ScopeErrorRenderer.ID;424}425426renderTemplate(container: HTMLElement): IScopeErrorTemplateData {427const wrapper = dom.append(container, $('.scope'));428const error = dom.append(wrapper, $('.error'));429return { error };430}431432renderElement(element: ITreeNode<IScope, FuzzyScore>, index: number, templateData: IScopeErrorTemplateData): void {433templateData.error.innerText = element.element.name;434}435436disposeTemplate(): void {437// noop438}439}440441export class VisualizedVariableRenderer extends AbstractExpressionsRenderer {442public static readonly ID = 'viz';443444/**445* Registers a helper that rerenders the tree when visualization is requested446* or cancelled./447*/448public static rendererOnVisualizationRange(model: IViewModel, tree: AsyncDataTree<any, any, any>): IDisposable {449return model.onDidChangeVisualization(({ original }) => {450if (!tree.hasNode(original)) {451return;452}453454const parent: IExpression = tree.getParentElement(original);455tree.updateChildren(parent, false, false);456});457458}459460constructor(461private readonly expressionRenderer: DebugExpressionRenderer,462@IDebugService debugService: IDebugService,463@IContextViewService contextViewService: IContextViewService,464@IHoverService hoverService: IHoverService,465@IMenuService private readonly menuService: IMenuService,466@IContextKeyService private readonly contextKeyService: IContextKeyService,467) {468super(debugService, contextViewService, hoverService);469}470471public override get templateId(): string {472return VisualizedVariableRenderer.ID;473}474475public override renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {476data.elementDisposable.clear();477super.renderExpressionElement(node.element, node, data);478}479480protected override renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {481const viz = expression as VisualizedExpression;482483let text = viz.name;484if (viz.value && typeof viz.name === 'string') {485text += ':';486}487data.label.set(text, highlights, viz.name);488data.elementDisposable.add(this.expressionRenderer.renderValue(data.value, viz, {489showChanged: false,490maxValueLength: 1024,491colorize: true,492session: expression.getSession(),493}));494}495496protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined {497const viz = <VisualizedExpression>expression;498return {499initialValue: expression.value,500ariaLabel: localize('variableValueAriaLabel', "Type new variable value"),501validationOptions: {502validation: () => viz.errorMessage ? ({ content: viz.errorMessage }) : null503},504onFinish: (value: string, success: boolean) => {505viz.errorMessage = undefined;506if (success) {507viz.edit(value).then(() => {508// Do not refresh scopes due to a node limitation #15520509forgetScopes = false;510this.debugService.getViewModel().updateViews();511});512}513}514};515}516517protected override renderActionBar(actionBar: ActionBar, expression: IExpression, _data: IExpressionTemplateData) {518const viz = expression as VisualizedExpression;519const contextKeyService = viz.original ? getContextForVariableMenuBase(this.contextKeyService, viz.original) : this.contextKeyService;520const context = viz.original ? getVariablesContext(viz.original) : undefined;521const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false });522523const { primary } = getContextMenuActions(menu, 'inline');524525if (viz.original) {526const action = toAction({527id: 'debugViz', label: localize('removeVisualizer', 'Remove Visualizer'), class: ThemeIcon.asClassName(Codicon.eye), run: () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)528});529action.checked = true;530primary.push(action);531actionBar.domNode.style.display = 'initial';532}533actionBar.clear();534actionBar.context = context;535actionBar.push(primary, { icon: true, label: false });536}537}538539export class VariablesRenderer extends AbstractExpressionsRenderer {540541static readonly ID = 'variable';542543constructor(544private readonly expressionRenderer: DebugExpressionRenderer,545@IMenuService private readonly menuService: IMenuService,546@IContextKeyService private readonly contextKeyService: IContextKeyService,547@IDebugVisualizerService private readonly visualization: IDebugVisualizerService,548@IContextMenuService private readonly contextMenuService: IContextMenuService,549@IDebugService debugService: IDebugService,550@IContextViewService contextViewService: IContextViewService,551@IHoverService hoverService: IHoverService,552) {553super(debugService, contextViewService, hoverService);554}555556get templateId(): string {557return VariablesRenderer.ID;558}559560protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {561data.elementDisposable.add(this.expressionRenderer.renderVariable(data, expression as Variable, {562highlights,563showChanged: true,564}));565}566567public override renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {568data.elementDisposable.clear();569super.renderExpressionElement(node.element, node, data);570}571572protected getInputBoxOptions(expression: IExpression): IInputBoxOptions {573const variable = <Variable>expression;574return {575initialValue: expression.value,576ariaLabel: localize('variableValueAriaLabel', "Type new variable value"),577validationOptions: {578validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null579},580onFinish: (value: string, success: boolean) => {581variable.errorMessage = undefined;582const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;583if (success && variable.value !== value && focusedStackFrame) {584variable.setVariable(value, focusedStackFrame)585// Need to force watch expressions and variables to update since a variable change can have an effect on both586.then(() => {587// Do not refresh scopes due to a node limitation #15520588forgetScopes = false;589this.debugService.getViewModel().updateViews();590});591}592}593};594}595596protected override renderActionBar(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData) {597const variable = expression as Variable;598const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable);599600const context = getVariablesContext(variable);601const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false });602const { primary } = getContextMenuActions(menu, 'inline');603604actionBar.clear();605actionBar.context = context;606actionBar.push(primary, { icon: true, label: false });607608const cts = new CancellationTokenSource();609data.elementDisposable.add(toDisposable(() => cts.dispose(true)));610this.visualization.getApplicableFor(expression, cts.token).then(result => {611data.elementDisposable.add(result);612613const originalExpression = (expression instanceof VisualizedExpression && expression.original) || expression;614const actions = result.object.map(v => toAction({ id: 'debugViz', label: v.name, class: v.iconClass || 'debug-viz-icon', run: this.useVisualizer(v, originalExpression, cts.token) }));615if (actions.length === 0) {616// no-op617} else if (actions.length === 1) {618actionBar.push(actions[0], { icon: true, label: false });619} else {620actionBar.push(toAction({ id: 'debugViz', label: localize('useVisualizer', 'Visualize Variable...'), class: ThemeIcon.asClassName(Codicon.eye), run: () => this.pickVisualizer(actions, originalExpression, data) }), { icon: true, label: false });621}622});623}624625private pickVisualizer(actions: IAction[], expression: IExpression, data: IExpressionTemplateData) {626this.contextMenuService.showContextMenu({627getAnchor: () => data.actionBar!.getContainer(),628getActions: () => actions,629});630}631632private useVisualizer(viz: DebugVisualizer, expression: IExpression, token: CancellationToken) {633return async () => {634const resolved = await viz.resolve(token);635if (token.isCancellationRequested) {636return;637}638639if (resolved.type === DebugVisualizationType.Command) {640viz.execute();641} else {642const replacement = await this.visualization.getVisualizedNodeFor(resolved.id, expression);643if (replacement) {644this.debugService.getViewModel().setVisualizedExpression(expression, replacement);645}646}647};648}649}650651class VariablesAccessibilityProvider implements IListAccessibilityProvider<IExpression | IScope> {652653getWidgetAriaLabel(): string {654return localize('variablesAriaTreeLabel', "Debug Variables");655}656657getAriaLabel(element: IExpression | IScope): string | null {658if (element instanceof Scope) {659return localize('variableScopeAriaLabel', "Scope {0}", element.name);660}661if (element instanceof Variable) {662return localize({ key: 'variableAriaLabel', comment: ['Placeholders are variable name and variable value respectivly. They should not be translated.'] }, "{0}, value {1}", element.name, element.value);663}664665return null;666}667}668669export const SET_VARIABLE_ID = 'debug.setVariable';670CommandsRegistry.registerCommand({671id: SET_VARIABLE_ID,672handler: (accessor: ServicesAccessor) => {673const debugService = accessor.get(IDebugService);674debugService.getViewModel().setSelectedExpression(variableInternalContext, false);675}676});677678CommandsRegistry.registerCommand({679metadata: {680description: COPY_VALUE_LABEL,681},682id: COPY_VALUE_ID,683handler: async (accessor: ServicesAccessor, arg: Variable | Expression | IVariablesContext | undefined, ctx?: (Variable | Expression)[]) => {684const debugService = accessor.get(IDebugService);685const clipboardService = accessor.get(IClipboardService);686let elementContext = '';687let elements: (Variable | Expression)[];688if (!arg) {689const viewService = accessor.get(IViewsService);690const focusedView = viewService.getFocusedView();691let view: IDebugViewWithVariables | null | undefined;692if (focusedView?.id === WATCH_VIEW_ID) {693view = viewService.getActiveViewWithId<IDebugViewWithVariables>(WATCH_VIEW_ID);694elementContext = 'watch';695} else if (focusedView?.id === VARIABLES_VIEW_ID) {696view = viewService.getActiveViewWithId<IDebugViewWithVariables>(VARIABLES_VIEW_ID);697elementContext = 'variables';698}699if (!view) {700return;701}702elements = view.treeSelection.filter(e => e instanceof Expression || e instanceof Variable);703} else if (arg instanceof Variable || arg instanceof Expression) {704elementContext = 'watch';705elements = [arg];706} else {707elementContext = 'variables';708elements = variableInternalContext ? [variableInternalContext] : [];709}710711const stackFrame = debugService.getViewModel().focusedStackFrame;712const session = debugService.getViewModel().focusedSession;713if (!stackFrame || !session || elements.length === 0) {714return;715}716717const evalContext = session.capabilities.supportsClipboardContext ? 'clipboard' : elementContext;718const toEvaluate = elements.map(element => element instanceof Variable ? (element.evaluateName || element.value) : element.name);719720try {721const evaluations = await Promise.all(toEvaluate.map(expr => session.evaluate(expr, stackFrame.frameId, evalContext)));722const result = coalesce(evaluations).map(evaluation => evaluation.body.result);723if (result.length) {724clipboardService.writeText(result.join('\n'));725}726} catch (e) {727const result = elements.map(element => element.value);728clipboardService.writeText(result.join('\n'));729}730}731});732733export const VIEW_MEMORY_ID = 'workbench.debug.viewlet.action.viewMemory';734735const HEX_EDITOR_EXTENSION_ID = 'ms-vscode.hexeditor';736const HEX_EDITOR_EDITOR_ID = 'hexEditor.hexedit';737738CommandsRegistry.registerCommand({739id: VIEW_MEMORY_ID,740handler: async (accessor: ServicesAccessor, arg: IVariablesContext | IExpression, ctx?: (Variable | Expression)[]) => {741const debugService = accessor.get(IDebugService);742let sessionId: string;743let memoryReference: string;744if ('sessionId' in arg) { // IVariablesContext745if (!arg.sessionId || !arg.variable.memoryReference) {746return;747}748sessionId = arg.sessionId;749memoryReference = arg.variable.memoryReference;750} else { // IExpression751if (!arg.memoryReference) {752return;753}754const focused = debugService.getViewModel().focusedSession;755if (!focused) {756return;757}758759sessionId = focused.getId();760memoryReference = arg.memoryReference;761}762763const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);764const editorService = accessor.get(IEditorService);765const notificationService = accessor.get(INotificationService);766const extensionService = accessor.get(IExtensionService);767const telemetryService = accessor.get(ITelemetryService);768769const ext = await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID);770if (ext || await tryInstallHexEditor(extensionsWorkbenchService, notificationService)) {771/* __GDPR__772"debug/didViewMemory" : {773"owner": "connor4312",774"debugType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }775}776*/777telemetryService.publicLog('debug/didViewMemory', {778debugType: debugService.getModel().getSession(sessionId)?.configuration.type,779});780781await editorService.openEditor({782resource: getUriForDebugMemory(sessionId, memoryReference),783options: {784revealIfOpened: true,785override: HEX_EDITOR_EDITOR_ID,786},787}, SIDE_GROUP);788}789}790});791792async function tryInstallHexEditor(extensionsWorkbenchService: IExtensionsWorkbenchService, notificationService: INotificationService): Promise<boolean> {793try {794await extensionsWorkbenchService.install(HEX_EDITOR_EXTENSION_ID, {795justification: localize("viewMemory.prompt", "Inspecting binary data requires this extension."),796enable: true797}, ProgressLocation.Notification);798return true;799} catch (error) {800notificationService.error(error);801return false;802}803}804805CommandsRegistry.registerCommand({806metadata: {807description: COPY_EVALUATE_PATH_LABEL,808},809id: COPY_EVALUATE_PATH_ID,810handler: async (accessor: ServicesAccessor, context: IVariablesContext | Variable) => {811const clipboardService = accessor.get(IClipboardService);812if (context instanceof Variable) {813await clipboardService.writeText(context.evaluateName!);814} else {815await clipboardService.writeText(context.variable.evaluateName!);816}817}818});819820CommandsRegistry.registerCommand({821metadata: {822description: ADD_TO_WATCH_LABEL,823},824id: ADD_TO_WATCH_ID,825handler: async (accessor: ServicesAccessor, context: IVariablesContext) => {826const debugService = accessor.get(IDebugService);827debugService.addWatchExpression(context.variable.evaluateName);828}829});830831registerAction2(class extends ViewAction<VariablesView> {832constructor() {833super({834id: 'variables.collapse',835viewId: VARIABLES_VIEW_ID,836title: localize('collapse', "Collapse All"),837f1: false,838icon: Codicon.collapseAll,839menu: {840id: MenuId.ViewTitle,841group: 'navigation',842when: ContextKeyExpr.equals('view', VARIABLES_VIEW_ID)843}844});845}846847runInView(_accessor: ServicesAccessor, view: VariablesView) {848view.collapseAll();849}850});851852853