Skip to content
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

Java adapter #106

Merged
merged 22 commits into from Sep 18, 2022
Merged

Java adapter #106

merged 22 commits into from Sep 18, 2022

Conversation

LDAP
Copy link
Contributor

@LDAP LDAP commented Mar 8, 2021

This PR adds Java support to the Debugger. LSP and LSP-jdtls are required for this adapter to work.

Closes #147
Closes #118
Closes sublimelsp/LSP-jdtls#24

@LDAP
Copy link
Contributor Author

LDAP commented Mar 8, 2021

Connection to the debugger works. But I am getting a "missing mainClass..." error.

Thats fixed by supplying:

"mainClass": "${file}",
"modulePaths": ["${folder}"]

Now the Debugger fails with:

jdtls: 08.03.2021, 15:28:47 [error response][launch]: Failed to launch debuggee in terminal. Reason: Failed to launch debuggee in terminal. Reason: java.util.concurrent.TimeoutException: timeout
Failed to launch debuggee in terminal. Reason: Failed to launch debuggee in terminal. Reason: java.util.concurrent.TimeoutException: timeout
com.microsoft.java.debug.core.DebugException: Failed to launch debuggee in terminal. Reason: Failed to launch debuggee in terminal. Reason: java.util.concurrent.TimeoutException: timeout
	at com.microsoft.java.debug.core.adapter.handler.LaunchWithDebuggingDelegate.lambda$launchInTerminal$0(LaunchWithDebuggingDelegate.java:157)
	at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
	at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2152)
	at com.microsoft.java.debug.core.protocol.AbstractProtocolServer$1.run(AbstractProtocolServer.java:192)
	at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
	at java.base/java.util.TimerThread.run(Timer.java:506)

Debugger does not reply to runInTerminal. Is this a known issue?
(I am running Windows 10, ST 4098)

@LDAP
Copy link
Contributor Author

LDAP commented Mar 8, 2021

Okey, I found the comment in terminal_process.py. Seems like ST4 does not support runInTerminal?

Comment on lines 74 to 75
url = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/vscjava/vsextensions/vscode-java-debug/latest/vspackage'
await adapter.vscode.install(self.type, url, log)
Copy link

Choose a reason for hiding this comment

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

@daveleroy I'm fairly certain this isn't allowed, unfortunately...

https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf

Offerings are intended for use only with Visual Studio Products and Services and you may only install and use Marketplace Offerings with Visual Studio Products and Services.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case its actually possible to compile the jar and ship it with Debugger (or download it elsewhere.) The plugin itself is under Eclipse Public License - v 1.0. See https://github.com/microsoft/java-debug/blob/master/LICENSE.txt

Copy link

Choose a reason for hiding this comment

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

You cannot download or in any way install software provided through the “marketplace” if it’s not a “visual studio product”. The license you link to does allow one to compile/use the software. But obtaining that software cannot be done through the marketplace (or, risk a cease and desist I guess).

Copy link

Choose a reason for hiding this comment

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

If that debugger jar is somewhere else on the internet available for download and the terms and conditions of that source allow it, then we can use that.

Copy link

Choose a reason for hiding this comment

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

Why do you think VSCodium has its own marketplace?

Copy link
Owner

Choose a reason for hiding this comment

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

We would want the full extension https://github.com/Microsoft/vscode-java-debug which includes all the snippets and a schema for the configurations.

Copy link

Choose a reason for hiding this comment

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

Incidentally, open-vsx.org is hosted by the eclipse foundation https://www.eclipse.org/community/eclipse_newsletter/2020/march/1.php

Copy link

@rwols rwols Mar 8, 2021

Choose a reason for hiding this comment

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

Right, I think you can also just fetch the .vsix file from https://github.com/microsoft/vscode-java-debug/releases/ and extract it as a zip.

Copy link
Owner

Choose a reason for hiding this comment

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

Looks like you can in this case. Other adapters do not add the vscode package to their releases so those will probably need to be changed.

Copy link

Choose a reason for hiding this comment

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

Yes. What a coincidence that the assets are not in a GitHub release... ;)

@daveleroy
Copy link
Owner

Okey, I found the comment in terminal_process.py. Seems like ST4 does not support runInTerminal?

It relies on winpty and dependencies don't work on the 3.8 environment

@daveleroy
Copy link
Owner

Okey, I found the comment in terminal_process.py. Seems like ST4 does not support runInTerminal?

@LDAP for now you can probably change your configuration to not use the integrated terminal.

"console": "internalConsole"

@LDAP
Copy link
Contributor Author

LDAP commented Mar 8, 2021

"console": "internalConsole"

In this case the Server errors with:

jdtls: 08.03.2021, 23:12:44 Error parsing message: com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
	at com.google.gson.internal.bind.TypeAdapters$35$1.read(TypeAdapters.java:897)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
	at com.google.gson.Gson.fromJson(Gson.java:932)
	at com.google.gson.Gson.fromJson(Gson.java:897)
	at com.google.gson.Gson.fromJson(Gson.java:846)
	at com.google.gson.Gson.fromJson(Gson.java:817)
	at com.microsoft.java.debug.core.protocol.JsonUtils.fromJson(JsonUtils.java:26)
	at com.microsoft.java.debug.core.protocol.AbstractProtocolServer.processData(AbstractProtocolServer.java:219)
	at com.microsoft.java.debug.core.protocol.AbstractProtocolServer.run(AbstractProtocolServer.java:98)
	at com.microsoft.java.debug.core.adapter.ProtocolServer.run(ProtocolServer.java:61)
	at com.microsoft.java.debug.plugin.internal.JavaDebugServer$2.run(JavaDebugServer.java:136)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:832)

jdtls: 08.03.2021, 23:12:44 Error parsing message: com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
	at com.google.gson.internal.bind.TypeAdapters$35$1.read(TypeAdapters.java:897)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
	at com.google.gson.Gson.fromJson(Gson.java:932)
	at com.google.gson.Gson.fromJson(Gson.java:897)
	at com.google.gson.Gson.fromJson(Gson.java:846)
	at com.google.gson.Gson.fromJson(Gson.java:817)
	at com.microsoft.java.debug.core.protocol.JsonUtils.fromJson(JsonUtils.java:26)
	at com.microsoft.java.debug.core.protocol.AbstractProtocolServer.processData(AbstractProtocolServer.java:219)
	at com.microsoft.java.debug.core.protocol.AbstractProtocolServer.run(AbstractProtocolServer.java:98)
	at com.microsoft.java.debug.core.adapter.ProtocolServer.run(ProtocolServer.java:61)
	at com.microsoft.java.debug.plugin.internal.JavaDebugServer$2.run(JavaDebugServer.java:136)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:832)

@daveleroy
Copy link
Owner

What request is this failing on? If its the initialize request its likely there are additional required parameters in the configuration that get patched in.

https://github.com/microsoft/java-debug/blob/64d42435b9913ad39f49f8700e0c046abdc12dd5/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java#L103

I would try adding "env":{}

@LDAP
Copy link
Contributor Author

LDAP commented Mar 9, 2021

What request is this failing on? If its the initialize request its likely there are additional required parameters in the configuration that get patched in.

https://github.com/microsoft/java-debug/blob/64d42435b9913ad39f49f8700e0c046abdc12dd5/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java#L103

I would try adding "env":{}

Still erroring,

this is the Debugger Protocol:

⟸ process/started
⟸ request/initialize(1) :: {'clientID': 'sublime', 'clientName': 'Sublime Text', 'adapterID': 'java', 'pathFormat': 'path', 'linesStartAt1': True, 'columnsStartAt1': True, 'supportsVariableType': True, 'supportsVariablePaging': False, 'supportsRunInTerminalRequest': True, 'locale': 'en-us'}
⟹ response/initialize(1) :: {'supportsConfigurationDoneRequest': True, 'supportsHitConditionalBreakpoints': True, 'supportsConditionalBreakpoints': True, 'supportsEvaluateForHovers': True, 'supportsCompletionsRequest': True, 'supportsRestartFrame': True, 'supportsSetVariable': True, 'supportsRestartRequest': False, 'supportTerminateDebuggee': True, 'supportsDelayedStackTraceLoading': False, 'supportsLogPoints': True, 'supportsExceptionInfoRequest': True, 'exceptionBreakpointFilters': [{'label': 'Uncaught Exceptions', 'filter': 'uncaught'}, {'label': 'Caught Exceptions', 'filter': 'caught'}], 'supportsDataBreakpoints': True, 'supportsClipboardContext': True}
⟸ request/launch(2) :: {'console': 'internalConsole', 'cwd': 'D:\\Local_Repositories\\Development\\Java\\Projects\\TimeSheetGenerator', 'env': {}, 'mainClass': 'D:\\Local_Repositories\\Development\\Java\\Projects\\TimeSheetGenerator\\src\\main\\java\\main\\Main.java', 'modulePaths': ['D:\\Local_Repositories\\Development\\Java\\Projects\\TimeSheetGenerator'], 'name': 'Launch Current File', 'request': 'launch', 'stopOnEntry': False, 'type': 'java'}
⟹ event/initialized :: {'type': 'initialized'}
⟸ request/setDataBreakpoints(3) :: {'breakpoints': []}
⟸ request/setExceptionBreakpoints(4) :: {'filters': [], 'filterOptions': []}
⟸ request/setBreakpoints(5) :: {'source': {'path': 'D:\\Local_Repositories\\Development\\Java\\Projects\\TimeSheetGenerator\\src\\main\\java\\main\\Main.java'}, 'breakpoints': [{'line': 29}]}
⟹ response/launch(2) :: None
⟸ request/threads(6) :: None
⟹ response/setDataBreakpoints(3) :: {'breakpoints': []}
⟹ response/setExceptionBreakpoints(4) :: None
⟹ response/setBreakpoints(5) :: {'breakpoints': [{'id': 1, 'verified': False, 'line': 29, 'message': ''}]}
⟸ request/configurationDone(7) :: None

Config:

{
	"type": "java",
	"name": "Launch Current File",
	"request": "launch",
	"mainClass": "${file}",
	"modulePaths": ["${folder}"],
	"cwd": "${folder}",
	"console": "internalConsole",
	"env": {},
	"stopOnEntry": false
},

@daveleroy
Copy link
Owner

Looks to be way past the initialize request. From the looks of it they are just not handling the configurationDone request correctly and treating the arguments as none optional.

https://github.com/daveleroy/sublime_debugger/blob/2b5f0a5860027a0f2ad9b9fcd791b41a3179da57/modules/dap/session.py#L672

Try changing await self.request('configurationDone', None) to await self.request('configurationDone', {})

@LDAP
Copy link
Contributor Author

LDAP commented Mar 9, 2021

Looks to be way past the initialize request. From the looks of it they are just not handling the configurationDone request correctly and treating the arguments as none optional.

https://github.com/daveleroy/sublime_debugger/blob/2b5f0a5860027a0f2ad9b9fcd791b41a3179da57/modules/dap/session.py#L672

Try changing await self.request('configurationDone', None) to await self.request('configurationDone', {})

That did the trick. Now the command gets executed, but fails too...

@LDAP
Copy link
Contributor Author

LDAP commented Mar 9, 2021

It's working now. Although I am still getting

Error parsing message: com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull

Settings:

{
	"type": "java",
	"name": "Launch",
	"request": "launch",
	"mainClass": "main.Main",
	"classPaths": ["${folder}/target/classes", "${folder}/target/lib/*"],
	"cwd": "${folder}",
	"console": "internalConsole",
},
(Its a Maven Project)

Error parsing message: com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull
@LDAP
Copy link
Contributor Author

LDAP commented Mar 9, 2021

Fixed that too. Since I changed the Protocol to {} instead of None, this may break other things?

@LDAP
Copy link
Contributor Author

LDAP commented Mar 9, 2021

@daveleroy How would an adapter add/set configuration variables? I would like to fetch classpath, mainclass,... with the Language Server because for example classpath can get very complicated on a big maven Project.

@daveleroy
Copy link
Owner

Fixed that too. Since I changed the Protocol to {} instead of None, this may break other things?

Unlikely to break anything and is probably what vscode does despite the threads request not having any arguments specified in the spec https://microsoft.github.io/debug-adapter-protocol/specification

@daveleroy
Copy link
Owner

@daveleroy How would an adapter add/set configuration variables? I would like to fetch classpath, mainclass,... with the Language Server because for example classpath can get very complicated on a big maven Project.

You can adjust a configuration after the variables are expanded but before it is sent to the adapter using async def configuration_resolve(self, configuration). See https://github.com/daveleroy/sublime_debugger/blob/2b5f0a5860027a0f2ad9b9fcd791b41a3179da57/modules/adapters/go.py#L29

@daveleroy
Copy link
Owner

If you are talking about adding custom variables that can't currently be done and would need support added for it

@LDAP
Copy link
Contributor Author

LDAP commented Mar 9, 2021

@daveleroy How would an adapter add/set configuration variables? I would like to fetch classpath, mainclass,... with the Language Server because for example classpath can get very complicated on a big maven Project.

You can adjust a configuration after the variables are expanded but before it is sent to the adapter using async def configuration_resolve(self, configuration). See

https://github.com/daveleroy/sublime_debugger/blob/2b5f0a5860027a0f2ad9b9fcd791b41a3179da57/modules/adapters/go.py#L29

That should work :)

@LDAP
Copy link
Contributor Author

LDAP commented Mar 10, 2021

Implemented auto-configuration of mainClass and classPaths allowing for a plug & play solution together with sublimelsp/LSP-jdtls#7

@LDAP LDAP marked this pull request as draft March 10, 2021 12:22
@LDAP
Copy link
Contributor Author

LDAP commented Mar 10, 2021

@daveleroy I get errors in the client.
Error:

<Future pending cb=[<TaskWakeupMethWrapper object at 0x000001C9BE368340>()]>
Debugger: error: adapter failed hover evaluation Evaluation failed because the thread is not suspended.
<Future pending cb=[<TaskWakeupMethWrapper object at 0x000001C9BE39DA90>()]>
Traceback (most recent call last):
  File "C:\Users\Lucas Alber\AppData\Roaming\Sublime Text 3\Packages\Debugger\modules\core\sublime_event_loop.py", line 105, in call_exception_handler
    raise context['exception']
  File "C:\Users\Lucas Alber\AppData\Roaming\Sublime Text 3\Packages\Debugger\modules\dap\session.py", line 739, in refresh_threads
    threads = array_from_json(dap.Thread.from_json, response['threads'])
KeyError: 'threads'

Protocol:

⟸ process/started
⟸ request/initialize(1) :: {'clientID': 'sublime', 'clientName': 'Sublime Text', 'adapterID': 'java', 'pathFormat': 'path', 'linesStartAt1': True, 'columnsStartAt1': True, 'supportsVariableType': True, 'supportsVariablePaging': False, 'supportsRunInTerminalRequest': True, 'locale': 'en-us'}
⟹ response/initialize(1) :: {'supportsConfigurationDoneRequest': True, 'supportsHitConditionalBreakpoints': True, 'supportsConditionalBreakpoints': True, 'supportsEvaluateForHovers': True, 'supportsCompletionsRequest': True, 'supportsRestartFrame': True, 'supportsSetVariable': True, 'supportsRestartRequest': False, 'supportTerminateDebuggee': True, 'supportsDelayedStackTraceLoading': False, 'supportsLogPoints': True, 'supportsExceptionInfoRequest': True, 'exceptionBreakpointFilters': [{'label': 'Uncaught Exceptions', 'filter': 'uncaught'}, {'label': 'Caught Exceptions', 'filter': 'caught'}], 'supportsDataBreakpoints': True, 'supportsClipboardContext': True}
⟸ request/launch(2) :: {'mainClass': 'org.lalber.tools.checkstyle.Main', 'name': 'Launch Java Program', 'request': 'launch', 'type': 'java', 'cwd': 'D:\\Local_Repositories\\Development\\Java\\Projects\\checkstylecompatibilitytest', 'classPaths': ['D:\\Local_Repositories\\Development\\Java\\Projects\\checkstylecompatibilitytest\\target\\classes', 'C:\\Users\\Lucas Alber\\.m2\\repository\\commons-io\\commons-io\\2.6\\commons-io-2.6.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\org\\fusesource\\jansi\\jansi\\1.18\\jansi-1.18.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\org\\antlr\\antlr4-runtime\\4.7.2\\antlr4-runtime-4.7.2.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\net\\sf\\saxon\\Saxon-HE\\9.9.1-5\\Saxon-HE-9.9.1-5.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\info\\picocli\\picocli\\4.1.1\\picocli-4.1.1.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\commons-beanutils\\commons-beanutils\\1.9.4\\commons-beanutils-1.9.4.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\commons-logging\\commons-logging\\1.2\\commons-logging-1.2.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\commons-collections\\commons-collections\\3.2.2\\commons-collections-3.2.2.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\com\\google\\guava\\guava\\28.1-jre\\guava-28.1-jre.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\com\\google\\guava\\failureaccess\\1.0.1\\failureaccess-1.0.1.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\com\\google\\guava\\listenablefuture\\9999.0-empty-to-avoid-conflict-with-guava\\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\com\\google\\code\\findbugs\\jsr305\\3.0.2\\jsr305-3.0.2.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\org\\checkerframework\\checker-qual\\2.8.1\\checker-qual-2.8.1.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\com\\google\\errorprone\\error_prone_annotations\\2.3.2\\error_prone_annotations-2.3.2.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\com\\google\\j2objc\\j2objc-annotations\\1.3\\j2objc-annotations-1.3.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\org\\codehaus\\mojo\\animal-sniffer-annotations\\1.18\\animal-sniffer-annotations-1.18.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\antlr\\antlr\\2.7.7\\antlr-2.7.7.jar', 'C:\\Users\\Lucas Alber\\.m2\\repository\\org\\apache\\commons\\commons-lang3\\3.9\\commons-lang3-3.9.jar'], 'console': 'internalConsole'}
⟹ event/initialized :: {'type': 'initialized'}
⟹ response/launch(2) :: None
⟸ request/threads(3) :: {}
⟸ request/setBreakpoints(4) :: {'source': {'path': 'D:\\Local_Repositories\\Development\\Java\\Projects\\checkstylecompatibilitytest\\src\\main\\java\\org\\lalber\\tools\\checkstyle\\Main.java'}, 'breakpoints': [{'line': 40}]}
⟸ request/setExceptionBreakpoints(5) :: {'filters': [], 'filterOptions': []}
⟸ request/setDataBreakpoints(6) :: {'breakpoints': []}
⟹ response/threads(3) :: {'threads': [{'id': 1, 'name': 'Thread [main]'}, {'id': 2, 'name': 'Thread [Reference Handler]'}, {'id': 3, 'name': 'Thread [Finalizer]'}, {'id': 4, 'name': 'Thread [Signal Dispatcher]'}, {'id': 5, 'name': 'Thread [Attach Listener]'}]}
⟹ response/setBreakpoints(4) :: {'breakpoints': [{'id': 1, 'verified': True, 'line': 40, 'message': ''}]}
⟹ response/setExceptionBreakpoints(5) :: None
⟹ response/setDataBreakpoints(6) :: {'breakpoints': []}
⟸ request/configurationDone(7) :: {}
⟹ response/configurationDone(7) :: None
⟹ event/thread :: {'reason': 'started', 'threadId': 6, 'type': 'thread'}
⟹ event/thread :: {'reason': 'started', 'threadId': 1, 'type': 'thread'}
⟸ request/threads(8) :: {}
⟸ request/threads(9) :: {}
⟹ event/thread :: {'reason': 'started', 'threadId': 7, 'type': 'thread'}
⟸ request/threads(10) :: {}
⟹ response/threads(8) :: {'threads': [{'id': 1, 'name': 'Thread [main]'}, {'id': 2, 'name': 'Thread [Reference Handler]'}, {'id': 3, 'name': 'Thread [Finalizer]'}, {'id': 4, 'name': 'Thread [Signal Dispatcher]'}, {'id': 5, 'name': 'Thread [Attach Listener]'}, {'id': 6, 'name': 'Thread [Notification Thread]'}, {'id': 7, 'name': 'Thread [Common-Cleaner]'}]}
⟹ event/breakpoint :: {'reason': 'new', 'breakpoint': {'id': 1, 'verified': True, 'line': 40, 'message': ''}, 'type': 'breakpoint'}
⟹ response/threads(9) :: {'threads': [{'id': 1, 'name': 'Thread [main]'}, {'id': 2, 'name': 'Thread [Reference Handler]'}, {'id': 3, 'name': 'Thread [Finalizer]'}, {'id': 4, 'name': 'Thread [Signal Dispatcher]'}, {'id': 5, 'name': 'Thread [Attach Listener]'}, {'id': 6, 'name': 'Thread [Notification Thread]'}, {'id': 7, 'name': 'Thread [Common-Cleaner]'}]}
⟹ response/threads(10) :: {'threads': [{'id': 1, 'name': 'Thread [main]'}, {'id': 2, 'name': 'Thread [Reference Handler]'}, {'id': 3, 'name': 'Thread [Finalizer]'}, {'id': 4, 'name': 'Thread [Signal Dispatcher]'}, {'id': 5, 'name': 'Thread [Attach Listener]'}, {'id': 6, 'name': 'Thread [Notification Thread]'}, {'id': 7, 'name': 'Thread [Common-Cleaner]'}]}
⟹ event/stopped :: {'threadId': 1, 'reason': 'breakpoint', 'allThreadsStopped': False, 'type': 'stopped'}
⟹ event/output :: {'category': 'stdout', 'output': 'Checkstyle-Kompatibilitätstest, null', 'variablesReference': 0, 'line': 0, 'column': 0, 'type': 'output'}
⟹ event/output :: {'category': 'stdout', 'output': '.\r\n', 'variablesReference': 0, 'line': 0, 'column': 0, 'type': 'output'}
⟸ request/threads(11) :: {}
⟸ request/stackTrace(12) :: {'threadId': 1}
⟹ response/threads(11) :: {'threads': [{'id': 1, 'name': 'Thread [main]'}, {'id': 2, 'name': 'Thread [Reference Handler]'}, {'id': 3, 'name': 'Thread [Finalizer]'}, {'id': 4, 'name': 'Thread [Signal Dispatcher]'}, {'id': 5, 'name': 'Thread [Attach Listener]'}, {'id': 6, 'name': 'Thread [Notification Thread]'}, {'id': 7, 'name': 'Thread [Common-Cleaner]'}]}
⟹ response/stackTrace(12) :: {'stackFrames': [{'id': 1, 'source': {'name': 'Main.java', 'path': 'D:\\Local_Repositories\\Development\\Java\\Projects\\checkstylecompatibilitytest\\src\\main\\java\\org\\lalber\\tools\\checkstyle\\Main.java', 'sourceReference': 0}, 'line': 40, 'column': 1, 'name': 'Main.main(String[])'}], 'totalFrames': 1}
⟸ request/scopes(13) :: {'frameId': 1}
⟹ response/scopes(13) :: {'scopes': [{'name': 'Local', 'variablesReference': 2, 'expensive': False}]}
⟸ request/variables(14) :: {'variablesReference': 2}
⟹ response/variables(14) :: {'variables': [{'name': 'args', 'value': 'String[0]@9', 'type': 'String[]', 'variablesReference': 0, 'namedVariables': 0, 'indexedVariables': 0, 'evaluateName': 'args'}]}
⟸ request/next(15) :: {'threadId': 1}

@LDAP
Copy link
Contributor Author

LDAP commented Mar 10, 2021

@daveleroy The Java Language Server returns URIs like:

jdt://contents/java.base/java.lang/Integer.class?=jdt.ls-java-project/D:%5C/Local_Repositories%5C/windows-dev-tools%5C/bins%5C/jdk%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/15%5C/docs%5C/api%5C/=/%3Cjava.lang(Integer.class

leading to an empty view. JDT.LS can actually provide the contents of the class file, does Debugger support that the server resolves the content itself?

@rwols
Copy link

rwols commented Aug 17, 2021

I've enabled LSP-jdtls to understand jdt: URI schemes. But the actual opening of the URI should be done from the DAP side. See: sublimelsp/LSP-jdtls#15 (comment)

@davidsoles
Copy link

The jar you are looking for is available at https://mvnrepository.com/artifact/com.microsoft.java/com.microsoft.java.debug.plugin and is exactly the same VSCode use to debug Java applications.
image

modules/source_navigation.py Outdated Show resolved Hide resolved
@LDAP
Copy link
Contributor Author

LDAP commented Sep 16, 2022

@daveleroy @rwols The current implementation (together with sublimelsp/LSP-jdtls#7) does work, but uses a dirty hack to get the view created by the LSP-plugin. Ideally, I could await a future which does return the created view.

@daveleroy
Copy link
Owner

You could just expose the request like we do for command: vscode.java.startDebugSession and then just call it from the debugger side. Then create and populate the view on the debugger side like a dap source request.

Also maybe that should be more generic since I think there are some other commands that need to get run as well to fetch configuration stuff. They seem to be also using command: vscode.java.resolveClasspath as well.

# LSP side
class DebuggerJdtlsExecuteCommandCommand(LspWindowCommand):
    session_name =  'jdtls'

    def run(self, id: int, command: str, arguments: 'Optional[list[Any]]' = None):       
        session =  self.session()
        if not session:
            self.handle_response(id, ValueError('Unable to find `jdtls` session'))
            return

        session.execute_command({"command": command, 'arguments': arguments}, False).then(lambda resp: self.handle_response(id, resp))

    def handle_response(self, id, response):
        if isinstance(response, Exception):
            self.window.run_command('debugger_lsp_jdtls_excute_command_response', {'id': id, 'reject': str(response)})
        else:
            self.window.run_command('debugger_lsp_jdtls_excute_command_response', {'id': id, 'resolve': response})
# Debugger Side
pending_futures: Dict[int, core.Future] = {}
pending_futures_current_id = 0

class DebuggerLspJdtlsResponseCommand(sublime_plugin.WindowCommand):
	def run(self, **args):
		future = pending_futures.get(args["id"])
		if not future:
			print("Hmm... unable to find a future port for this id")
			return

		if error := args.get('reject'):
			future.set_exception(core.Error(error))
		else:
			future.set_result(args.get('resolve'))

async def jdtls_execute_command(self, command: str, arguments: list[Any]|None = None):
	# probably need to add some sort of timeout
	# probably need to ensure lsp_jdts is installed
	# probably need to ensure lsp_jdts has the plugin jar patched in
	future = core.Future()
	sublime.set_timeout(lambda: future.cancel(), 2500)

	global pending_futures_current_id
	id = pending_futures_current_id
	pending_futures_current_id += 1
	pending_futures[id] = future

	# ask lsp_jdts to start the debug adapter
	# lsp_jdts will call debugger_lsp_jdts_command_response with the id it was given and a port to connect to the adapter with or an error
	# note: the active window might not match the debugger window but generally will... probably need a way to get the actual window.
	sublime.active_window().run_command('debugger_jdtls_execute_command', {
		'id': id,
		'command': command,
		'arguments': arguments
	})

	try:
		return await future
	except CancelledError:
		raise core.Error('Unable to execute `jdtls` command (timed out)')
port: int = await jdtls_execute_command('vscode.java.startDebugSession'))

@daveleroy
Copy link
Owner

Not sure what the difference between send_request_async and execute_command is here but I guess you would need to expose send_request_async to perform java/classFileContents

@LDAP
Copy link
Contributor Author

LDAP commented Sep 17, 2022

That should work. Then the only thing left is to move the special case into the Java adapter instead of the core module.

@LDAP
Copy link
Contributor Author

LDAP commented Sep 17, 2022

@daveleroy The Java Adapter should work now. You might want to change the on_source_navigation hook. I was not sure how to handle the special case.

@LDAP LDAP marked this pull request as ready for review September 17, 2022 10:49
@LDAP
Copy link
Contributor Author

LDAP commented Sep 17, 2022

The configuration logic now uses a generic bridge which allows sending requests to the LSP.

raise core.Error('LSP and LSP-jdtls required to debug Java!')

# Get configuration from LSP
lsp_config = await self.get_configuration_from_lsp()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might change that next week to only request if the user did not provide certain config option, by splitting in multiple methods. I should also use the correct main class in the checkProjectSettings command below.

@daveleroy
Copy link
Owner

daveleroy commented Sep 18, 2022

@LDAP I'm merging this if its not ready for release it can be put behind the dev flag when I make the next package control update

@daveleroy daveleroy merged commit a0b678c into daveleroy:master Sep 18, 2022
@LDAP
Copy link
Contributor Author

LDAP commented Sep 18, 2022

@daveleroy Alright. Please do not release it until I fixed the user configuration overwrite. I'll open a follow up PR in the next few days.

@LDAP
Copy link
Contributor Author

LDAP commented Sep 18, 2022

I'll notify you when I release the new LSP-jdtls version containing the necessary debug plugin and bridge.

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

Successfully merging this pull request may close these issues.

DAP for LSP-jdtls Java Support Yet? Java adaptor.
4 participants