Path: blob/main/src/vs/workbench/contrib/debug/browser/variablesView.ts
3296 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 { Action, IAction } 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}231232return e instanceof Variable && !e.presentationHint?.attributes?.includes('readOnly') && !e.presentationHint?.lazy;233}234235private async onContextMenu(e: ITreeContextMenuEvent<IExpression | IScope>): Promise<void> {236const variable = e.element;237if (!(variable instanceof Variable) || !variable.value) {238return;239}240241return openContextMenuForVariableTreeElement(this.contextKeyService, this.menuService, this.contextMenuService, MenuId.DebugVariablesContext, e);242}243}244245export async function openContextMenuForVariableTreeElement(parentContextKeyService: IContextKeyService, menuService: IMenuService, contextMenuService: IContextMenuService, menuId: MenuId, e: ITreeContextMenuEvent<IExpression | IScope>) {246const variable = e.element;247if (!(variable instanceof Variable) || !variable.value) {248return;249}250251const contextKeyService = await getContextForVariableMenuWithDataAccess(parentContextKeyService, variable);252const context: IVariablesContext = getVariablesContext(variable);253const menu = menuService.getMenuActions(menuId, contextKeyService, { arg: context, shouldForwardArgs: false });254255const { secondary } = getContextMenuActions(menu, 'inline');256contextMenuService.showContextMenu({257getAnchor: () => e.anchor,258getActions: () => secondary259});260}261262const getVariablesContext = (variable: Variable): IVariablesContext => ({263sessionId: variable.getSession()?.getId(),264container: variable.parent instanceof Expression265? { expression: variable.parent.name }266: (variable.parent as (Variable | Scope)).toDebugProtocolObject(),267variable: variable.toDebugProtocolObject()268});269270/**271* Gets a context key overlay that has context for the given variable, including data access info.272*/273async function getContextForVariableMenuWithDataAccess(parentContext: IContextKeyService, variable: Variable) {274const session = variable.getSession();275if (!session || !session.capabilities.supportsDataBreakpoints) {276return getContextForVariableMenuBase(parentContext, variable);277}278279const contextKeys: [string, unknown][] = [];280const dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference);281const dataBreakpointId = dataBreakpointInfoResponse?.dataId;282const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes;283setDataBreakpointInfoResponse(dataBreakpointInfoResponse);284285if (!dataBreakpointAccessTypes) {286contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);287} else {288for (const accessType of dataBreakpointAccessTypes) {289switch (accessType) {290case 'read':291contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.key, !!dataBreakpointId]);292break;293case 'write':294contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.key, !!dataBreakpointId]);295break;296case 'readWrite':297contextKeys.push([CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.key, !!dataBreakpointId]);298break;299}300}301}302303return getContextForVariableMenuBase(parentContext, variable, contextKeys);304}305306/**307* Gets a context key overlay that has context for the given variable.308*/309function getContextForVariableMenuBase(parentContext: IContextKeyService, variable: Variable, additionalContext: [string, unknown][] = []) {310variableInternalContext = variable;311return getContextForVariable(parentContext, variable, additionalContext);312}313314function isStackFrame(obj: any): obj is IStackFrame {315return obj instanceof StackFrame;316}317318class VariablesDataSource extends AbstractExpressionDataSource<IStackFrame | null, IExpression | IScope> {319320public override hasChildren(element: IStackFrame | null | IExpression | IScope): boolean {321if (!element) {322return false;323}324if (isStackFrame(element)) {325return true;326}327328return element.hasChildren;329}330331protected override doGetChildren(element: IStackFrame | IExpression | IScope): Promise<(IExpression | IScope)[]> {332if (isStackFrame(element)) {333return element.getScopes();334}335336return element.getChildren();337}338}339340interface IScopeTemplateData {341name: HTMLElement;342label: HighlightedLabel;343}344345class VariablesDelegate implements IListVirtualDelegate<IExpression | IScope> {346347getHeight(element: IExpression | IScope): number {348return 22;349}350351getTemplateId(element: IExpression | IScope): string {352if (element instanceof ErrorScope) {353return ScopeErrorRenderer.ID;354}355356if (element instanceof Scope) {357return ScopesRenderer.ID;358}359360if (element instanceof VisualizedExpression) {361return VisualizedVariableRenderer.ID;362}363364return VariablesRenderer.ID;365}366}367368class ScopesRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeTemplateData> {369370static readonly ID = 'scope';371372get templateId(): string {373return ScopesRenderer.ID;374}375376renderTemplate(container: HTMLElement): IScopeTemplateData {377const name = dom.append(container, $('.scope'));378const label = new HighlightedLabel(name);379380return { name, label };381}382383renderElement(element: ITreeNode<IScope, FuzzyScore>, index: number, templateData: IScopeTemplateData): void {384templateData.label.set(element.element.name, createMatches(element.filterData));385}386387disposeTemplate(templateData: IScopeTemplateData): void {388templateData.label.dispose();389}390}391392interface IScopeErrorTemplateData {393error: HTMLElement;394}395396class ScopeErrorRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeErrorTemplateData> {397398static readonly ID = 'scopeError';399400get templateId(): string {401return ScopeErrorRenderer.ID;402}403404renderTemplate(container: HTMLElement): IScopeErrorTemplateData {405const wrapper = dom.append(container, $('.scope'));406const error = dom.append(wrapper, $('.error'));407return { error };408}409410renderElement(element: ITreeNode<IScope, FuzzyScore>, index: number, templateData: IScopeErrorTemplateData): void {411templateData.error.innerText = element.element.name;412}413414disposeTemplate(): void {415// noop416}417}418419export class VisualizedVariableRenderer extends AbstractExpressionsRenderer {420public static readonly ID = 'viz';421422/**423* Registers a helper that rerenders the tree when visualization is requested424* or cancelled./425*/426public static rendererOnVisualizationRange(model: IViewModel, tree: AsyncDataTree<any, any, any>): IDisposable {427return model.onDidChangeVisualization(({ original }) => {428if (!tree.hasNode(original)) {429return;430}431432const parent: IExpression = tree.getParentElement(original);433tree.updateChildren(parent, false, false);434});435436}437438constructor(439private readonly expressionRenderer: DebugExpressionRenderer,440@IDebugService debugService: IDebugService,441@IContextViewService contextViewService: IContextViewService,442@IHoverService hoverService: IHoverService,443@IMenuService private readonly menuService: IMenuService,444@IContextKeyService private readonly contextKeyService: IContextKeyService,445) {446super(debugService, contextViewService, hoverService);447}448449public override get templateId(): string {450return VisualizedVariableRenderer.ID;451}452453public override renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {454data.elementDisposable.clear();455super.renderExpressionElement(node.element, node, data);456}457458protected override renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {459const viz = expression as VisualizedExpression;460461let text = viz.name;462if (viz.value && typeof viz.name === 'string') {463text += ':';464}465data.label.set(text, highlights, viz.name);466data.elementDisposable.add(this.expressionRenderer.renderValue(data.value, viz, {467showChanged: false,468maxValueLength: 1024,469colorize: true,470session: expression.getSession(),471}));472}473474protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined {475const viz = <VisualizedExpression>expression;476return {477initialValue: expression.value,478ariaLabel: localize('variableValueAriaLabel', "Type new variable value"),479validationOptions: {480validation: () => viz.errorMessage ? ({ content: viz.errorMessage }) : null481},482onFinish: (value: string, success: boolean) => {483viz.errorMessage = undefined;484if (success) {485viz.edit(value).then(() => {486// Do not refresh scopes due to a node limitation #15520487forgetScopes = false;488this.debugService.getViewModel().updateViews();489});490}491}492};493}494495protected override renderActionBar(actionBar: ActionBar, expression: IExpression, _data: IExpressionTemplateData) {496const viz = expression as VisualizedExpression;497const contextKeyService = viz.original ? getContextForVariableMenuBase(this.contextKeyService, viz.original) : this.contextKeyService;498const context = viz.original ? getVariablesContext(viz.original) : undefined;499const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false });500501const { primary } = getContextMenuActions(menu, 'inline');502503if (viz.original) {504const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined));505action.checked = true;506primary.push(action);507actionBar.domNode.style.display = 'initial';508}509actionBar.clear();510actionBar.context = context;511actionBar.push(primary, { icon: true, label: false });512}513}514515export class VariablesRenderer extends AbstractExpressionsRenderer {516517static readonly ID = 'variable';518519constructor(520private readonly expressionRenderer: DebugExpressionRenderer,521@IMenuService private readonly menuService: IMenuService,522@IContextKeyService private readonly contextKeyService: IContextKeyService,523@IDebugVisualizerService private readonly visualization: IDebugVisualizerService,524@IContextMenuService private readonly contextMenuService: IContextMenuService,525@IDebugService debugService: IDebugService,526@IContextViewService contextViewService: IContextViewService,527@IHoverService hoverService: IHoverService,528) {529super(debugService, contextViewService, hoverService);530}531532get templateId(): string {533return VariablesRenderer.ID;534}535536protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {537data.elementDisposable.add(this.expressionRenderer.renderVariable(data, expression as Variable, {538highlights,539showChanged: true,540}));541}542543public override renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {544data.elementDisposable.clear();545super.renderExpressionElement(node.element, node, data);546}547548protected getInputBoxOptions(expression: IExpression): IInputBoxOptions {549const variable = <Variable>expression;550return {551initialValue: expression.value,552ariaLabel: localize('variableValueAriaLabel', "Type new variable value"),553validationOptions: {554validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null555},556onFinish: (value: string, success: boolean) => {557variable.errorMessage = undefined;558const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;559if (success && variable.value !== value && focusedStackFrame) {560variable.setVariable(value, focusedStackFrame)561// Need to force watch expressions and variables to update since a variable change can have an effect on both562.then(() => {563// Do not refresh scopes due to a node limitation #15520564forgetScopes = false;565this.debugService.getViewModel().updateViews();566});567}568}569};570}571572protected override renderActionBar(actionBar: ActionBar, expression: IExpression, data: IExpressionTemplateData) {573const variable = expression as Variable;574const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable);575576const context = getVariablesContext(variable);577const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false });578const { primary } = getContextMenuActions(menu, 'inline');579580actionBar.clear();581actionBar.context = context;582actionBar.push(primary, { icon: true, label: false });583584const cts = new CancellationTokenSource();585data.elementDisposable.add(toDisposable(() => cts.dispose(true)));586this.visualization.getApplicableFor(expression, cts.token).then(result => {587data.elementDisposable.add(result);588589const originalExpression = (expression instanceof VisualizedExpression && expression.original) || expression;590const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, originalExpression, cts.token)));591if (actions.length === 0) {592// no-op593} else if (actions.length === 1) {594actionBar.push(actions[0], { icon: true, label: false });595} else {596actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, originalExpression, data)), { icon: true, label: false });597}598});599}600601private pickVisualizer(actions: IAction[], expression: IExpression, data: IExpressionTemplateData) {602this.contextMenuService.showContextMenu({603getAnchor: () => data.actionBar!.getContainer(),604getActions: () => actions,605});606}607608private useVisualizer(viz: DebugVisualizer, expression: IExpression, token: CancellationToken) {609return async () => {610const resolved = await viz.resolve(token);611if (token.isCancellationRequested) {612return;613}614615if (resolved.type === DebugVisualizationType.Command) {616viz.execute();617} else {618const replacement = await this.visualization.getVisualizedNodeFor(resolved.id, expression);619if (replacement) {620this.debugService.getViewModel().setVisualizedExpression(expression, replacement);621}622}623};624}625}626627class VariablesAccessibilityProvider implements IListAccessibilityProvider<IExpression | IScope> {628629getWidgetAriaLabel(): string {630return localize('variablesAriaTreeLabel', "Debug Variables");631}632633getAriaLabel(element: IExpression | IScope): string | null {634if (element instanceof Scope) {635return localize('variableScopeAriaLabel', "Scope {0}", element.name);636}637if (element instanceof Variable) {638return localize({ key: 'variableAriaLabel', comment: ['Placeholders are variable name and variable value respectivly. They should not be translated.'] }, "{0}, value {1}", element.name, element.value);639}640641return null;642}643}644645export const SET_VARIABLE_ID = 'debug.setVariable';646CommandsRegistry.registerCommand({647id: SET_VARIABLE_ID,648handler: (accessor: ServicesAccessor) => {649const debugService = accessor.get(IDebugService);650debugService.getViewModel().setSelectedExpression(variableInternalContext, false);651}652});653654CommandsRegistry.registerCommand({655metadata: {656description: COPY_VALUE_LABEL,657},658id: COPY_VALUE_ID,659handler: async (accessor: ServicesAccessor, arg: Variable | Expression | IVariablesContext | undefined, ctx?: (Variable | Expression)[]) => {660if (!arg) {661const viewService = accessor.get(IViewsService);662const view = viewService.getActiveViewWithId(WATCH_VIEW_ID) || viewService.getActiveViewWithId(VARIABLES_VIEW_ID);663if (view) {664665}666}667const debugService = accessor.get(IDebugService);668const clipboardService = accessor.get(IClipboardService);669let elementContext = '';670let elements: (Variable | Expression)[];671if (!arg) {672const viewService = accessor.get(IViewsService);673const focusedView = viewService.getFocusedView();674let view: IDebugViewWithVariables | null | undefined;675if (focusedView?.id === WATCH_VIEW_ID) {676view = viewService.getActiveViewWithId<IDebugViewWithVariables>(WATCH_VIEW_ID);677elementContext = 'watch';678} else if (focusedView?.id === VARIABLES_VIEW_ID) {679view = viewService.getActiveViewWithId<IDebugViewWithVariables>(VARIABLES_VIEW_ID);680elementContext = 'variables';681}682if (!view) {683return;684}685elements = view.treeSelection.filter(e => e instanceof Expression || e instanceof Variable);686} else if (arg instanceof Variable || arg instanceof Expression) {687elementContext = 'watch';688elements = ctx ? ctx : [];689} else {690elementContext = 'variables';691elements = variableInternalContext ? [variableInternalContext] : [];692}693694const stackFrame = debugService.getViewModel().focusedStackFrame;695const session = debugService.getViewModel().focusedSession;696if (!stackFrame || !session || elements.length === 0) {697return;698}699700const evalContext = session.capabilities.supportsClipboardContext ? 'clipboard' : elementContext;701const toEvaluate = elements.map(element => element instanceof Variable ? (element.evaluateName || element.value) : element.name);702703try {704const evaluations = await Promise.all(toEvaluate.map(expr => session.evaluate(expr, stackFrame.frameId, evalContext)));705const result = coalesce(evaluations).map(evaluation => evaluation.body.result);706if (result.length) {707clipboardService.writeText(result.join('\n'));708}709} catch (e) {710const result = elements.map(element => element.value);711clipboardService.writeText(result.join('\n'));712}713}714});715716export const VIEW_MEMORY_ID = 'workbench.debug.viewlet.action.viewMemory';717718const HEX_EDITOR_EXTENSION_ID = 'ms-vscode.hexeditor';719const HEX_EDITOR_EDITOR_ID = 'hexEditor.hexedit';720721CommandsRegistry.registerCommand({722id: VIEW_MEMORY_ID,723handler: async (accessor: ServicesAccessor, arg: IVariablesContext | IExpression, ctx?: (Variable | Expression)[]) => {724const debugService = accessor.get(IDebugService);725let sessionId: string;726let memoryReference: string;727if ('sessionId' in arg) { // IVariablesContext728if (!arg.sessionId || !arg.variable.memoryReference) {729return;730}731sessionId = arg.sessionId;732memoryReference = arg.variable.memoryReference;733} else { // IExpression734if (!arg.memoryReference) {735return;736}737const focused = debugService.getViewModel().focusedSession;738if (!focused) {739return;740}741742sessionId = focused.getId();743memoryReference = arg.memoryReference;744}745746const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);747const editorService = accessor.get(IEditorService);748const notificationService = accessor.get(INotificationService);749const extensionService = accessor.get(IExtensionService);750const telemetryService = accessor.get(ITelemetryService);751752const ext = await extensionService.getExtension(HEX_EDITOR_EXTENSION_ID);753if (ext || await tryInstallHexEditor(extensionsWorkbenchService, notificationService)) {754/* __GDPR__755"debug/didViewMemory" : {756"owner": "connor4312",757"debugType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }758}759*/760telemetryService.publicLog('debug/didViewMemory', {761debugType: debugService.getModel().getSession(sessionId)?.configuration.type,762});763764await editorService.openEditor({765resource: getUriForDebugMemory(sessionId, memoryReference),766options: {767revealIfOpened: true,768override: HEX_EDITOR_EDITOR_ID,769},770}, SIDE_GROUP);771}772}773});774775async function tryInstallHexEditor(extensionsWorkbenchService: IExtensionsWorkbenchService, notificationService: INotificationService): Promise<boolean> {776try {777await extensionsWorkbenchService.install(HEX_EDITOR_EXTENSION_ID, {778justification: localize("viewMemory.prompt", "Inspecting binary data requires this extension."),779enable: true780}, ProgressLocation.Notification);781return true;782} catch (error) {783notificationService.error(error);784return false;785}786}787788CommandsRegistry.registerCommand({789metadata: {790description: COPY_EVALUATE_PATH_LABEL,791},792id: COPY_EVALUATE_PATH_ID,793handler: async (accessor: ServicesAccessor, context: IVariablesContext) => {794const clipboardService = accessor.get(IClipboardService);795await clipboardService.writeText(context.variable.evaluateName!);796}797});798799CommandsRegistry.registerCommand({800metadata: {801description: ADD_TO_WATCH_LABEL,802},803id: ADD_TO_WATCH_ID,804handler: async (accessor: ServicesAccessor, context: IVariablesContext) => {805const debugService = accessor.get(IDebugService);806debugService.addWatchExpression(context.variable.evaluateName);807}808});809810registerAction2(class extends ViewAction<VariablesView> {811constructor() {812super({813id: 'variables.collapse',814viewId: VARIABLES_VIEW_ID,815title: localize('collapse', "Collapse All"),816f1: false,817icon: Codicon.collapseAll,818menu: {819id: MenuId.ViewTitle,820group: 'navigation',821when: ContextKeyExpr.equals('view', VARIABLES_VIEW_ID)822}823});824}825826runInView(_accessor: ServicesAccessor, view: VariablesView) {827view.collapseAll();828}829});830831832