Skip to content

Conversation

@DeityLamb
Copy link
Contributor

Implement #67

@cla-bot
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
Contributor Author

@cla-bot check

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

cla-bot bot commented Aug 14, 2025

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

@DeityLamb
Copy link
Contributor 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
Contributor 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
Contributor 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
Contributor 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
Contributor 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.

@DeityLamb
Copy link
Contributor Author

@valentinegb It would be great if we could merge these changes soon
I’d like to start working on the next functionality :)

@valentinegb
Copy link
Collaborator

When testing this on my machine, the language server fails to start with the following logged by Zed:

2025-08-19T14:48:06-07:00 ERROR [lsp] Server reset connection for a request "initialize" id 0 2025-08-19T14:48:06-07:00 ERROR [lsp] Shutdown request failure, server jdtls (id 5): server shut down 2025-08-19T14:48:06-07:00 ERROR [lsp] cannot read LSP message headers 2025-08-19T14:48:06-07:00 ERROR [lsp] Broken pipe (os error 32) 2025-08-19T14:48:06-07:00 ERROR [project::lsp_store] Failed to start language server "jdtls": Error { context: "initializing server jdtls, id 5", source: "Server reset the connection", } 2025-08-19T14:48:06-07:00 ERROR [project::lsp_store] server stderr: [eval]:1 import { Buffer } from "node:buffer"; ^^^^^^ SyntaxError: Cannot use import statement outside a module at makeContextifyScript (node:internal/vm:185:14) at node:internal/process/execution:107:22 at [eval]-wrapper:6:24 at runScript (node:internal/process/execution:101:62) at evalScript (node:internal/process/execution:136:3) at node:internal/main/eval_string:51:3 Node.js v22.5.1 
@DeityLamb
Copy link
Contributor Author

Fixed, the minimum required Node version is now v16.x

@gayanper
Copy link

Any plans to release this soon?

@DeityLamb
Copy link
Contributor Author

DeityLamb commented Aug 22, 2025

@valentinegb
Maybe some more explanations with the code are needed, or specific changes?
Or is it just the approach that doesn’t feel right?
I’m totally open to any feedback and happy to rework or test whatever is needed

It would also be great if you could let me know when you’ll have time for a review
Or maybe someone else could take over the review of this PR?

I’m really eager to start implementing the contentProvider for sources and decompiled dependencies,
test runners, and other functionality
Unfortunately, without these changes I can’t move forward

Copy link
Collaborator

@valentinegb valentinegb left a comment

Choose a reason for hiding this comment

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

Hey! Really sorry I'm late to review this again ^^'
I think as far as I'm concerned, this PR looks good and I approve it :>

On that note though I should probably say that this project should look for a new maintainer. Honestly, I'm not even really a Java developer and I was just interested in working on this project to improve the experience for myself while I had to use Java for a class. But, now I don't have that class, so I can't help but let my interest dwindle, considering I haven't even touched Java in a while. I'm not a member of @zed-extensions so I will leave it to them to find another maintainer

@valentinegb valentinegb merged commit 12a7250 into zed-extensions:main Aug 22, 2025
1 check passed
@pranav-dream11
Copy link

pranav-dream11 commented Aug 28, 2025

@DeityLamb i am getting this error when i was trying the extension out today

error: "Failed to send request to lsp proxy error sending request for url (http://localhost:52357/)\n\nCaused by:\n 0: client error (Connect)\n 1: tcp connect error: Connection refused (os error 61)\n 2: Connection refused (os error 61)"

@DeityLamb
Copy link
Contributor Author

DeityLamb commented Aug 28, 2025

@pranav-dream11 This happens when the LSP isn’t running. Try restarting the JDTLS extension and wait a bit before debugging. Properly handling all lifecycles with all the required workarounds is nearly impossible, sorry :)

I’ll try to figure something out with it later

@pranav-dream11
Copy link

@DeityLamb thanks, it works amazingly well

@NirakhRastogi
Copy link

I am using this debug configuration

// // For more documentation on how to configure debug tasks, // see: https://zed.dev/docs/debugger [ { "adapter": "Java", "request": "launch", "label": "run", "mainClass": "Test", "stopOnEntry": true } ] 

and this is my class

import java.util.Scanner; public class Test { private static String abc = "Hello"; public static void main(String[] args) { Scanner sc = new Scanner(System.in); int a = sc.nextInt(); System.out.println("Hello World"); System.out.println(abc); System.out.println(a); sc.close(); } } 

When trying to run using debugger, getting following error
error: "Debugger plugin is not loaded

Env is windows and extensions installed are Java v6.4.1, Java with Eclipse JDTLS v0.2.5.

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

5 participants