Merge pull request #274414 from microsoft/brchen/debug-context-menu

Fix watch view context menu when Variables pane is hidden
This commit is contained in:
Bryan Chen
2025-11-06 13:27:50 -08:00
committed by GitHub
4 changed files with 45 additions and 10 deletions
@@ -566,8 +566,8 @@ export class DebugSession implements IDebugSession {
return this._dataBreakpointInfo({ name: address, bytes, asAddress: true });
}
dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
return this._dataBreakpointInfo({ name, variablesReference });
dataBreakpointInfo(name: string, variablesReference?: number, frameId?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
return this._dataBreakpointInfo({ name, variablesReference, frameId });
}
private async _dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
@@ -23,6 +23,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../pla
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js';
@@ -68,7 +69,8 @@ export class WatchExpressionsView extends ViewPane implements IDebugViewWithVari
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@IHoverService hoverService: IHoverService,
@IMenuService private readonly menuService: IMenuService
@IMenuService private readonly menuService: IMenuService,
@ILogService private readonly logService: ILogService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
@@ -221,15 +223,14 @@ export class WatchExpressionsView extends ViewPane implements IDebugViewWithVari
const selection = this.tree.getSelection();
const contextKeyService = element && await getContextForWatchExpressionMenuWithDataAccess(this.contextKeyService, element);
const contextKeyService = element && await getContextForWatchExpressionMenuWithDataAccess(this.contextKeyService, element, this.debugService, this.logService);
const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: element, shouldForwardArgs: false });
const { secondary } = getContextMenuActions(menu, 'inline');
// const actions = getFlatContextMenuActions(this.menu.getActions({ arg: element, shouldForwardArgs: true }));
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => secondary,
getActionsContext: () => element && selection.includes(element) ? selection : element ? [element] : [],
getActionsContext: () => element && selection.includes(element) ? selection : element ? [element] : []
});
}
}
@@ -409,14 +410,48 @@ function getContextForWatchExpressionMenu(parentContext: IContextKeyService, exp
/**
* Gets a context key overlay that has context for the given expression, including data access info.
*/
async function getContextForWatchExpressionMenuWithDataAccess(parentContext: IContextKeyService, expression: IExpression) {
async function getContextForWatchExpressionMenuWithDataAccess(parentContext: IContextKeyService, expression: IExpression, debugService: IDebugService, logService: ILogService) {
const session = expression.getSession();
if (!session || !session.capabilities.supportsDataBreakpoints) {
return getContextForWatchExpressionMenu(parentContext, expression);
}
const contextKeys: [string, unknown][] = [];
const dataBreakpointInfoResponse = await session.dataBreakpointInfo('evaluateName' in expression ? expression.evaluateName as string : expression.name);
const stackFrame = debugService.getViewModel().focusedStackFrame;
let dataBreakpointInfoResponse;
try {
// Per DAP spec:
// - If evaluateName is available: use it as an expression (top-level evaluation)
// - Otherwise, check if it's a Variable: use name + parent reference (container-relative)
// - Otherwise: use name as an expression
if ('evaluateName' in expression && expression.evaluateName) {
// Use evaluateName if available (more precise for evaluation context)
dataBreakpointInfoResponse = await session.dataBreakpointInfo(
expression.evaluateName as string,
undefined,
stackFrame?.frameId
);
} else if (expression instanceof Variable) {
// Variable without evaluateName: use name relative to parent container
dataBreakpointInfoResponse = await session.dataBreakpointInfo(
expression.name,
expression.parent.reference,
stackFrame?.frameId
);
} else {
// Expression without evaluateName: use name as the expression to evaluate
dataBreakpointInfoResponse = await session.dataBreakpointInfo(
expression.name,
undefined,
stackFrame?.frameId
);
}
} catch (error) {
// silently continue without data breakpoint support for this item
logService.error('Failed to get data breakpoint info for watch expression:', error);
}
const dataBreakpointId = dataBreakpointInfoResponse?.dataId;
const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes;
setDataBreakpointInfoResponse(dataBreakpointInfoResponse);
@@ -445,7 +445,7 @@ export interface IDebugSession extends ITreeElement, IDisposable {
sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise<void>;
sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise<void>;
dataBreakpointInfo(name: string, variablesReference?: number): Promise<IDataBreakpointInfoResponse | undefined>;
dataBreakpointInfo(name: string, variablesReference?: number, frameId?: number): Promise<IDataBreakpointInfoResponse | undefined>;
dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined>;
sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise<void>;
sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise<void>;
@@ -235,7 +235,7 @@ export class MockSession implements IDebugSession {
throw new Error('Method not implemented.');
}
dataBreakpointInfo(name: string, variablesReference?: number | undefined): Promise<{ dataId: string | null; description: string; canPersist?: boolean | undefined } | undefined> {
dataBreakpointInfo(name: string, variablesReference?: number | undefined, frameId?: number | undefined): Promise<{ dataId: string | null; description: string; canPersist?: boolean | undefined } | undefined> {
throw new Error('Method not implemented.');
}