diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 74ec0c9785b..34cf1145a7a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -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> { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 428d762b4d5..a2c421aaf1e 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -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); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 286b0c0f315..be135eff8f4 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -445,7 +445,7 @@ export interface IDebugSession extends ITreeElement, IDisposable { sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise; sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise; - dataBreakpointInfo(name: string, variablesReference?: number): Promise; + dataBreakpointInfo(name: string, variablesReference?: number, frameId?: number): Promise; dataBytesBreakpointInfo(address: string, bytes: number): Promise; sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise; diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index e559c23fa11..0007966084a 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -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.'); }