- 
                Notifications
    
You must be signed in to change notification settings  - Fork 35.9k
 
Fix watch view context menu when Variables pane is hidden #274414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…sion data access - Add optional frameId parameter to IDebugSession.dataBreakpointInfo and implementations (DebugSession, MockSession) so adapters can resolve data breakpoint info with frame context. - Update watch expressions context logic to pass the DebugService into getContextForWatchExpressionMenuWithDataAccess. - Enhance data breakpoint lookup for watch items: - For Variable instances, ensure parent children are fetched and pass the parent's variablesReference. - For expressions, pass frameId for complex expressions (e.g. contains '.' or '[') so adapters can parse them. - For simple variable names, fetch the local scope's reference and variables before querying. - Wrap calls in try/catch to silently continue if dataBreakpointInfo fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request adds support for the optional frameId parameter to the dataBreakpointInfo method in the debug adapter protocol. This allows the debug adapter to resolve data breakpoint information for expressions within the context of a specific stack frame.
- Updates the 
dataBreakpointInfomethod signature across interfaces, implementations, and mocks to include the optionalframeIdparameter - Implements logic to properly pass 
frameIdfor complex expressions orvariablesReferencefor simple variables when requesting data breakpoint information - Adds error handling to silently continue if 
dataBreakpointInfofails - Removes trailing comma in a function call
 
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description | 
|---|---|
| src/vs/workbench/contrib/debug/common/debug.ts | Updates the IDebugSession.dataBreakpointInfo method signature to include the optional frameId parameter | 
| src/vs/workbench/contrib/debug/browser/debugSession.ts | Updates the dataBreakpointInfo implementation to accept and pass the frameId parameter to the underlying debug adapter | 
| src/vs/workbench/contrib/debug/test/common/mockDebug.ts | Updates the mock implementation to match the new method signature | 
| src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts | Implements the logic to determine when to use variablesReference vs frameId based on expression type, adds error handling, and removes a trailing comma | 
| 
               | 
          ||
| // For simple expressions (variable names), use the local scope reference | ||
| // For complex expressions (like s.x), pass frameId and let the adapter parse it | ||
| if (expressionName.includes('.') || expressionName.includes('[')) { | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expression detection logic using simple string contains checks for '.' and '[' is fragile. This approach won't correctly handle all valid JavaScript/TypeScript expressions (e.g., member access via bracket notation with complex keys, or variables with these characters in their names). Consider using a more robust expression parsing method or documenting the limitations of this approach.
| let scopeReference = 0; | ||
| if (stackFrame) { | ||
| const scopes = await stackFrame.getScopes(); | ||
| const localScope = scopes.find(s => !s.expensive); // Local scope is typically not expensive | 
    
      
    
      Copilot
AI
    
    
    
      Oct 31, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to find the local scope by checking !s.expensive is unreliable and based on an assumption that may not hold for all debug adapters. Consider using a more explicit identifier for the local scope (e.g., checking scope name or type) or documenting this assumption more clearly with its potential failure cases.
| const localScope = scopes.find(s => !s.expensive); // Local scope is typically not expensive | |
| let localScope = scopes.find(s => typeof s.name === 'string' && s.name.toLowerCase() === 'local'); | |
| if (!localScope) { | |
| localScope = scopes.find(s => typeof s.name === 'string' && s.name.toLowerCase() === 'locals'); | |
| } | |
| // Fallback: if not found by name, use the old logic (not expensive) | |
| if (!localScope) { | |
| localScope = scopes.find(s => !s.expensive); | |
| } | 
| scopeReference | ||
| ); | ||
| } | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think passing the frameId is good but I'm confused why we need to look at scopes or check if the expression is nested. evaluateName is always expected to be based on the stackframe, so a scope reference is not needed. If there isn't an evaluate name, we should evaluate expression.name in the parent context which is expression.parent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the Variables window is off, the scopes are not populated so here we have to populate the scope and pass it in.
From my test, if we don't pass a scope reference here, the dataBreakpointInfo() throws "Unable to find child property".
Regarding the expression.parent, from my debug, it is undefined for a top-level expression like s.x (accessing a member variable in a struct object) or a (a simple variable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure why we need scopes. There's nothing in DAP that ties scopes to the dataBreakpointInfo. It needs only a name, and optionally variablesReference/frameId. We didn't pass the frameId before, and you fixed that here, which I think is good.
Regarding the expression.parent, from my debug, it is undefined for a top-level expression like s.x (accessing a member variable in a struct object) or a (a simple variable)
Yea, that is intended. It's only defined for children of DAP Variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment
Problem
The context menu in the Watch view fails to appear during debugging when the Variables pane is hidden/collapsed. This prevents users from accessing debugging actions like "Break on Value Change", "Copy Value", etc.
Fixes: #273721
Root Cause
When the Variables pane is hidden, we never fetches scope variables from the debug adapter. When the Watch view tries to get data breakpoint info for expressions, the debug adapter doesn't have the necessary variable context and throws "Unable to find child property" errors.
Solution
Before requesting data breakpoint info, ensure the debug adapter has the necessary context:
For nested variable
s(like x within expanded s):variablesReferenceFor simple expressions (like
sora):variablesReferenceFor complex expressions (like
s.x):frameIdand let the adapter parse itError handling:
dataBreakpointInfo()calls in try-catch