Skip to content

Add support for debugger #71

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

DeityLamb
Copy link

Implement #67

Copy link

cla-bot bot commented Aug 14, 2025

We require contributors to sign our Contributor License Agreement, and we don't have @DeityLamb on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@DeityLamb
Copy link
Author

@cla-bot check

@cla-bot cla-bot bot added the cla-signed label Aug 14, 2025
Copy link

cla-bot bot commented Aug 14, 2025

The cla-bot has been summoned, and re-checked this pull request!

@DeityLamb
Copy link
Author

DeityLamb commented Aug 14, 2025

I’ve implemented downloading the debug plugin,
injecting it into initialization_options,
and setting up the LSP proxy (i'll redo it later, the code is a bit messy).

Right now, the DAP port is passed via a ./port.txt file in the project root.
This is just a temporary solution
if I keep it, I’d at least move it to something like /tmp/zed-debugger/{workspace_hash}.

I haven’t been able to get the debugger fully running yet, still figuring out exactly what’s required for that.
It does seem to connect, but then fails with the following error

2025-08-13T22:36:03+03:00 INFO  [dap::transport] Debug adapter has connected to TCP server 127.0.0.1:39371
2025-08-13T22:36:03+03:00 ERROR [debugger_ui::debugger_panel] Failed to attach to remote debuggee VM. Reason: java.io.IOException: Failed to attach to null:0 (attach timeout 30000, handshake timeout 0)

@DeityLamb
Copy link
Author

Okay, I’ve made some progress with the debugger

I added a schema for the request options, and I managed to try running the debugger without it crashing

For now, it seems that the debugger, for some reason, can’t detect the classpaths in the project
I set up a minimal test environment with just gradle init
configured the debug scenario

[
  {
    "adapter": "Java",
    "request": "launch",
    "label": "run",
    "mainClass": "org.example.App",
    "classPaths": ["$Auto"],
    "stopOnEntry": true
  }
]

And for every request I get

Error: Could not find or load main class org.example.App
Caused by: java.lang.ClassNotFoundException: org.example.App

DAP logs, it seems it cannot connect to the process

Aug 14, 2025 10:53:34 PM com.microsoft.java.debug.core.UsageDataSession recordInfo
INFO: launch debug info
Aug 14, 2025 10:53:34 PM com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler launch
INFO: Trying to launch Java Program with options:
main-class: org.example.App
args:
module-path:
class-path: $Auto
vmArgs:
Aug 14, 2025 10:53:34 PM com.microsoft.java.debug.core.adapter.handler.LaunchWithDebuggingDelegate lambda$launchInTerminal$0
INFO: Launching debuggee in terminal console succeeded.
Aug 14, 2025 10:53:34 PM com.microsoft.java.debug.core.adapter.handler.LaunchUtils findJavaProcessInTerminalShell
INFO: Retried 1 times but failed to find Java subProcess of shell pid 20208
Aug 14, 2025 10:53:34 PM com.microsoft.java.debug.core.UsageDataSession submitUsageData
INFO: session usage data summary
Aug 14, 2025 10:53:34 PM com.microsoft.java.debug.plugin.internal.JavaDebugServer$2 run
INFO: Debug connection closed

@DeityLamb
Copy link
Author

DeityLamb commented Aug 14, 2025

Oh, looks like $Auto is a VSCode specific option :)
I decided to check right after I replied.
If specify the classpaths directly, everything works 🎉

"classPaths": ["app/build/classes/java/main"]

It seems this part will also need to be implemented

Well, the debugger itself works, and that’s good
Aside from that, I just need to set up the scenarios and figure out how to correctly get the port

@DeityLamb
Copy link
Author

Okay, I’ll summarize what’s ready so far.

Downloading and integrating the java-debug plugin.
Launching jdtls via the proxy.mjs file, which opens an HTTP port and processes LSP requests.
On the extension side, a client is implemented to interact with the proxy server.

We obtain the port for launching the debugger via a direct request, so there’s no issue with that anymore.
The debugger itself generally works, but without auto-filling the config it can be problematic to use efficiently.

I tried adding auto-detection of the main class, project, and class paths ($Auto, $Test, $Runtime), and it works great for typical scenarios, but for example, I couldn’t run tests in all projects.

The current filling logic is as follows:
A call is made to vscode.java.resolveMainClass
with the arguments [config.mainClass, config.projectName] (None values are filtered out).

The result is matched against our current config (if it has any values), then we take the first mainClass and projectName from the matches (if any), and if there are no matches, we use the config values even if they are None.

Scope determination in class paths:

  • if classPaths have no scope values — pass them directly
  • if classPaths have scope values or are empty — fill in according to the following priority:
// https://github.com/microsoft/vscode-java-debug/blob/main/src/configurationProvider.ts#L518
let scope = {
    if classpaths.iter().any(|class| class == TEST_SCOPE) {
        Some("test".to_string())
    } else if classpaths.iter().any(|class| class == AUTO_SCOPE) {
        None
    } else if classpaths.iter().any(|class| class == RUNTIME_SCOPE) {
        Some("runtime".to_string())
    } else {
        None
    }
};

Then we call vscode.java.resolveClasspath with the arguments [main_class, project_name, scope].

We remove duplicates and aliases from the class paths.
We assign the obtained values to our debug config.

When testing, this approach didn’t work for every project (particularly when running tests).

@gayanper could you suggest how this might be better implemented?

@gayanper
Copy link

If i understand the last part which is related to running tests

The test execution support is implemented differently in a different extension in vscode. When running tests we have to do the following

  • figure out which test runtime is used
  • use the test runtime engine main class with parameters for current test class file, if a single method is selected, use method name as well.

Given that majority of real world projects are either maven or gradle, we could basically use either or those CLIs to run the tests in debug mode and use that port to connect the zed DAP client.

@DeityLamb DeityLamb marked this pull request as ready for review August 17, 2025 10:50
@DeityLamb
Copy link
Author

Okay, it looks like I’ve finished the main functionality

Unfortunately, we can’t implement the launch scenario yet, because java-debug builds the classpath for running on its own, and we can’t create a config from an existing command
On top of that, starting a command and then attaching to it is also not possible due to limitations in zed_extension_api.
Command only allows getting a result

I think it might be possible to create another workaround for task/test runners,
but probably not within the scope of this pr

Currently, scenarios for running the application as well as attaching to a process by PID or port are working

Open for review @valentinegb @gayanper

Copy link

@gayanper gayanper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks Good to Me from a design point of view, specially how Java LS and DAP is setup. I cannot comment must on the Rust and Zed specific things though since I'm new as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants