-
Notifications
You must be signed in to change notification settings - Fork 13
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
Variables and Variable Decorations #16
Variables and Variable Decorations #16
Conversation
@thegecko, I know you said you wanted small changes... but as I started down the road to variables, it just seemed better to implement the more general infrastructure along the way. |
|
||
export const activate = async (context: vscode.ExtensionContext): Promise<void> => { | ||
export const activate = async (context: vscode.ExtensionContext): Promise<AdapterRegistry> => { |
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.
If we are returning an API, it could be good to publish the API to npm (or even on the GitHub npm registry).
We do this internally by having an api folder with the interface exposed (which is published as a separate package) and then have the main class implement this interface.
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 that's a good idea, since it will make it easier to inform people of changes. On the one hand, I think that allowing others to contribute handling for their own adapters is valuable, on the other hand managing the API when VSCode's dependency resolution is fairly crude could be tricky. I know that we would like to support at least memory watchpoints on the plugin side - is there anything that falls on the margins of the DAP that you'd like to support? Perhaps we can start releasing the API once we've got at least a first draft of the functionality we know we want?
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.
Using our browser debugger and the gdb debugger as test adapters should really help us uncover the correct abstraction points and any common code we don't need to duplicate in plugins. Let's let this evolve as we add features and see where it goes :)
@@ -0,0 +1,137 @@ | |||
/******************************************************************************** |
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.
Our use case doesn't use gdb (and gdb doesn't work in the browser), so I'd be keen to see a basic adaptertracker implemented which does as much as possible, perhaps with an example gdb one extending the base one?
For example, scopes, variables and evaluation requests are all supported in DAP (we could check the capabilities request?) and even the local
variables can be detected using the DAP presentation hints.
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.
Sounds good - I think the only GDB specific bit in the current implementation is the actual evaluate request, in the sense that even though the evaluate request is part of the DAP, what expressions get sent and their syntax may depend on the language and adapter being used. Another tricky thing, though, is that the GDB adapter (currently) sends its memory references formatted as pointers rather than addresses, so without the evaluate request, it isn't possible to match the variables to memory at all. Other adapters may work around the specification a little less and provide addresses in that field, but we're shooting in the dark a bit.
Some questions for now... I'm going to look at getting the new features working with our adapter which may suggest a better abstraction point and/or default functionality. We may even be able to get away with a common tracker.... |
|
||
await memoryProvider.activate(context); | ||
const registry = new AdapterRegistry(); | ||
context.subscriptions.push(registry); |
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.
Prefer the architecture of having an activate
function in the class isolating functionality together.
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 prefer not to have a two-step lifecycle of construction and then activation if it can be avoided, so if the class is going to handle its own lifecycle, I'd rather it do it in the constructor. What do you think of that approach?
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.
My rationale for isolation is it keeps the activation needs along with the module being added. It makes it trivial to add/remove/change these modules and keeps the extension activation function from growing too large (a common problem I've seen in many extensions).
A couple of reasons why the two-step approach works better:
- Constructors aren't asynchronous, but often activation is :(. This can be worked around using static class factories, but makes it a little messy
- Instantiation is different to activation, we need to consider scenarios where classes may need to be deactivated, too (exposing a deactivate function, called in the global deactivate).
I'm not too precious about this, so happy to try adding the context to all constructors if you'd prefer?
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.
Sounds fair. Consistency has some value, too, so I'll switch the registry to use an activate
method, as well.
I've been unable to set up an environment which triggers the variables functionality (even with gdb), could I have some hints?
|
Hm, my setup wasn't complex: I used the program from the example workspace from the old Theia CPP extension, built and ran as described there, and the variables worked as expected. I did add one complex variable to that program when I was checking the sequence of scope request -> variable request -> variable request (when resolving fields), but just viewing the |
- Add logging to variable retrieval - Refactor into abstract variable tracker class
@thegecko, I've pushed up some changes that (may) address (some) of your comments:
|
Thanks, I'll give it a try.
That's exactly what I meant! |
OK, I have this working with gdb (there are a few hard-coded adapter type checks still in the code). I'd like to get this working with out adapter too... |
Are there particular checks you'd like me to fix, or are you going to see what it takes to get it working with your adapter so that those changes can be included here? |
Back on this today. Is there any reason we restrict the variables view to locals only? Variables in the global scope work for us in the existing theia-based memory inspector, but are ignored with this. e.g all non-register scopes are shown: |
import { logger } from '../logger'; | ||
|
||
class GdbAdapterTracker extends AdapterVariableTracker { | ||
protected override async variableToVariableRange(variable: DebugProtocol.Variable, session: vscode.DebugSession): Promise<VariableRange | undefined> { |
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 this approach should be the default that other implementations can optionally override. Using &(ref) and sizeof(ref) seem quite common in adapters.
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.
They're fairly C-specific, I'd say. I know that e.g. the LLDB Rust adapter can't use them. Maybe rather than tying it to GDB, it can be extracted as a function with the logger as an additional argument, so the logic can be used by other implementations, but not built into the default?
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.
That could be a good approach
|
||
class GdbAdapterTracker extends AdapterVariableTracker { | ||
protected override async variableToVariableRange(variable: DebugProtocol.Variable, session: vscode.DebugSession): Promise<VariableRange | undefined> { | ||
if (!variable.memoryReference || this.currentFrame === undefined) { |
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.
Should this check be for variable.name
as that is what is used to lookup the data?
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 not sure. The existence of memoryReference
is what tells us that the adapter believes it's something it can look up in memory. I don't believe that it would be legal, either, to send a variable with no name, but we can add it as an additional check to make sure.
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.
Checking the previous implementation (specifically reading global variables) it's possible to have a named variable without a memoryReference
. In my tests on our adapter, all global variables have a name used for the expression, but no memoryReference
(and it works in the old inspector)
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.
We can implement that, but it's non-DAP-adherent behavior on the part of that adapter. The old implementation predated the addition of memoryReference
to the interface for variables - it predated the readMemory
command entirely; now that that's available, it's supposed to be the most reliable indicator that the adapter can handle memory-related requests for that 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.
Erk, its the only way this works with our adapter.
I'll need to investigate populating the memoryReference when we only have a &ref
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.
That's all the GDB adapter has, right? It just sends &name
as its memory references. Which I think isn't exactly what the DAP intends, but it does at least indicate how the adapter wants the client to refer to the variable's memory location. In any case, I've pushed a change that checks only the name for those eval requests.
Just faulty memory / reading, I think. For some reason I thought the old implementation was limited to Locals, but you're quite right it wasn't. I'll adjust this and make sure the tree is flexible enough to handle other scopes. |
I think we are blocked on getting this working in the browser debugger due to some shortcomings in it. So in the interests of progress, let's merge this. It can always be refactored later. |
What it does
It also:
Long
s withBigInt
s (they can go inSet
s)entry-points
,plugin
-side code,webview
-side code, andcommon
code that works in both plugin and webview contexts.Open questions:
How to test
Review checklist
Reminder for reviewers