How do I listen to Debugger events within my own intellij Java/Gradle plugin?
First, I want to explain "why" - 'what is the goal I want to achieve'
I am currently working in Java/Kotlin code bases that are very complex; written in a very "spaghetti-code" way.
I often find myself stepping through this code, observing the values that show up under the "variables" section of the debug panel.
So, in order to help make this process easier, I want to write a plugin, that will be able to keep track of two main things:
- Keep track of a sequence of method names, in chronological order of execution
- Keep track of each of the variables and their values that were in-scope within each of these methods
What I will do is then use the method names and variable values to build a diagram where each box in the diagram is a method. When I hover over the method, I will be able to see what variable/values were in scope upon entering and exiting the method.
How am I attempting to reach this goal (above)?
I want to listen to debugger events. So that every time I hit a breakpoint, my plugin will execute code that will record:
- The function in which the breakpoint was hit
- The current variable/values that are in scope when the breakpoint was hit
I know that "projectListeners" and "applicationListeners" can be defined inside a plugin.xml file. Then the intellij plugin can have a class that @Override annotates a method so that this override method 'listens' to when the original method would get hit.
The information I'm looking for is this:
- Can you provide a complete example with the plugin.xml and the related class @Override method. This example would display a "Hello World" message whenever a breakpoint is hit in an intellij editor that has the plugin loaded
- If it's not possible to 'listen' to the debugger event in this way, could you provide a complete code example, that accomplishes showing a "Hello World" message when a breakpoint is hit, using a strategy that will work?
Thank you very much to whoever is able to help me, as a brand new intellij plugin developer.
cheers!
请先登录再写评论。
Hi, you can detect a new debug session adding a new XDebuggerManager listener:
project.messageBus.connect(lifetime).subscribe(XDebuggerManager.TOPIC, object : XDebuggerManagerListener {
override fun processStarted(debugProcess: XDebugProcess) {...}}
Then inside you can add a session listener:
debugProcess.session.addSessionListener(object : XDebugSessionListener {override fun sessionPaused() {...}})
This listener is called every time the debugger is stopped on a breakpoint or after a step. When session is stopped, you can get the current frame with session.getCurrentStackFrame(); and then collect visible variables there using the computeChildren method.
If you wouldn't mind answering another question...
This first part works like a charm, thank you for pointing me in the right direction towards subscribing to debugger events; The code that I have is correctly firing events
- Debugger started event fires correctly
- Debugger paused event fires correctly
I still have a question about the computeChildren method.
I am trying to get the variable values, but it looks like those values may be calculated asynchronously?
Here is what I have so far in Java:
computeChildren requires a new XCompositeNode() { ... } parameter. So I let intellij help me find out what override methods were available, including a method called addChildren.
addChildren seems like it could be called whenever the children (variables) are set to the variables panel in the debugger.
XValue childValue is an object that contains the "name" of the variable. However, I don't see the value loaded in this object. I just see the variable name.
Do I have to call a different function on childValue, in order to wait for the value to load, or is there a different way I should be using computeChildren to get the values?
Would you mind showing me a sample of how the variable values can be loaded with computeChildren?
Cheers!
All variable names in the frame (done)Just revisiting this to see if I can find out how to retrieve debugger values by looking at the platform.api.jar...
I see in this file:
com\intellij\xdebugger\frame\XCompositeNode.java
It looks like it says:
Still seeing if I can find a way to get debugger values.
Any examples for retrieving debugger variable values from the currentStackFrame are greatly appreciated
XStackFrame is a container of local variables, to collect them (async) you need to call computeChildren, for an example see the implementation of com.intellij.xdebugger.XDebuggerTestUtil#collectChildren (https://github.com/JetBrains/intellij-community/blob/992017ff8b554cf51b67110e6d30a402aaefd1ab/platform/xdebugger-testFramework/src/com/intellij/xdebugger/XDebuggerTestUtil.java#L180).
You'll obtain a List of XValue objects, from there you'll be able to obtain a representation of the values using computePresentation, for an example see https://github.com/JetBrains/intellij-community/blob/992017ff8b554cf51b67110e6d30a402aaefd1ab/platform/xdebugger-testFramework/src/com/intellij/xdebugger/XDebuggerTestUtil.java#L273
Thank you for your patience.
I am having difficult understanding how I can use the example code inside my plugin.
In the example, I see there is an XValueContainer.computeChildren (line 196).
Alternately, in my code, I have an XStackFrame.computeChildren. I see that XStackFrame extends XValueContainer and also has a computeChildren method.
XStackFrame.computeChildren seems to require a parameter of type XCompositeNode.
Alternatively, the example instantiates an XTestCompositeNode to pass to the XValueContainer.computeChildren method.
I'm not able to instantiate XCompositeNode in the same way as XTestCompositeNode since XCompositeNode requires the full XCompositeNode interface to implemented with @Override methods.
So I'm wondering if I need to use an object other than XCompositeNode. For example, I can get this code to compile, although it throws a runtime error because the tree parameter is null. I'm substituting XCompositeNode with a XStackFrameNode in this attempt, instead:
I'm continuing to work on this issue. I'm not sure what to try next, and I may be missing the obvious.
Thank you again for your patience.
Based on the explanation of my attempts to retrieve the values from the XStackFrame local variables container, do you have any pointers for what I should try next?
Cheers!
Make your own implementation of XCompositeNode, something like this:
This is brilliant; it helped out a lot. I'm very grateful for your help. I was able to get past my roadblock.
Would you mind helping with my next goal?
I am looking at how to get the name of the function in which the debugger is currently paused. I can see what I'm looking for within the local variables of the objects in my debugger. See image below... the name of the function is "greeting":
I'm guessing that I may have to obtain the method name asynchronously, just like getting variable values, described above.
what compute method... or other method can I use to get the name of the current method name? And will I be calling this compute method on the currentStackFrame object or a different object? Or am I overthinking this time because maybe I can just grab this method name synchronously?
Public debugger API has only com.intellij.xdebugger.frame.XStackFrame#customizePresentation method, you provide a container and it will be populated with a position presentation (file name, line number, etc.)
Method name is java debugger specific, to be able to get it you need to cast your xframe to java frame (and so depend on java debugger impl). After that follow what you saw in the debug view: ((JavaStackFrame)xframe).getStackFrameProxy().location().method().name()
It looks like JavaStackFrame is special, in that I can't use the usual Alt + Enter to choose to import it.
Is there an additional way to include this dependency into the file, for example, something to add to my gradle build task? Or do I need to add something to my plugin.xml file in order to be able to use the JavaStackFrame object?
@Milligangregg try to add a dependency on Java plugin: `intellij { plugins = ['java'] }`, depedency on java plugin module should be also defined in plugin.xml
Alexander Zolotov I think this is what I need here:
This may be a straight forward and simple question, since I'm new to intellij plugin development; Is this an existing module that I would need to download first? If so, where is the best place to download from? I'm looking to see if I can find out now...
> com.intellij.debugger.engine.JavaStackFrame
Yes, it's in Java plugin that you need to attach (as I described before)
No, you don't need to download anything additionally
Thank you, Alexander. This worked for me.
In addition to adding this to the build.gradle file:
I also had to add this to the plugin.xml file:
First, I tried this: com.intellij.java which seemed to allow me to use JavaStackFrame, but then I invalidated cache and restarted intellij and it no longer worked. So I switched to using com.intellij.modules.java instead and it seemed to work, even after invalidating cache and restarting intellij.
Cheers!
Thanks for detailed reply, I believe it might be useful for other users.
Hello Milligangregg , did you create any plugin from the above code if yes can I see the code?
Hello:
I also need to use JavaStackFrame and don't know how to include the dependency in the build.gradle.kts. I am using 2023.1.5 Ultimate Edition. Could someone please guide me. Thank you for your time and efforts.