Skip to content

Java adapter - #106

Merged
daveleroy merged 22 commits into
daveleroy:masterfrom
LDAP:java-adapter
Sep 18, 2022
Merged

daveleroy merged 22 commits into
daveleroy:masterfrom
LDAP:java-adapter

Conversation

@LDAP

@LDAP LDAP commented Mar 8, 2021 •

Copy link
Copy Markdown
Contributor

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

LDAP commented Mar 8, 2021 •

Copy link
Copy Markdown
Contributor Author

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

LDAP commented Mar 8, 2021

Copy link
Copy Markdown
Contributor Author

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

Comment thread modules/adapters/java.py Outdated
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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

@rwols rwols Mar 8, 2021 •

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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

LDAP commented Mar 8, 2021

Copy link
Copy Markdown
Contributor Author

"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
Copy Markdown
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

LDAP commented Mar 9, 2021

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

LDAP commented Mar 9, 2021

Copy link
Copy Markdown
Contributor Author

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

LDAP commented Mar 9, 2021

Copy link
Copy Markdown
Contributor Author

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

LDAP commented Mar 9, 2021

Copy link
Copy Markdown
Contributor Author

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

@LDAP

LDAP commented Mar 9, 2021

Copy link
Copy Markdown
Contributor Author

@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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Owner

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

@LDAP

LDAP commented Mar 9, 2021

Copy link
Copy Markdown
Contributor Author

@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

LDAP commented Mar 10, 2021

Copy link
Copy Markdown
Contributor Author

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

LDAP commented Mar 10, 2021 •

Copy link
Copy Markdown
Contributor Author

@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

LDAP commented Mar 10, 2021

Copy link
Copy Markdown
Contributor Author

@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?

@davidsoles

Copy link
Copy Markdown

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

Comment thread modules/source_navigation.py Outdated
@LDAP

LDAP commented Sep 16, 2022 •

Copy link
Copy Markdown
Contributor Author

@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
Copy Markdown
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
Copy Markdown
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

LDAP commented Sep 17, 2022

Copy link
Copy Markdown
Contributor Author

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

@LDAP

LDAP commented Sep 17, 2022

Copy link
Copy Markdown
Contributor Author

@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

LDAP commented Sep 17, 2022

Copy link
Copy Markdown
Contributor Author

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

Comment thread modules/adapters/java.py
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
Copy Markdown
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

daveleroy commented Sep 18, 2022 •

Copy link
Copy Markdown
Owner

@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

LDAP commented Sep 18, 2022

Copy link
Copy Markdown
Contributor Author

@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

LDAP commented Sep 18, 2022

Copy link
Copy Markdown
Contributor Author

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

@clankill3r

Copy link
Copy Markdown

@LDAP Is there any way you can share a minimal java project that just prints hello world? I just can't get it done, and neither I can find more info of what I could try next.

It can never find the main class and also:

the declared package "crap" does not match the expected package ""

I think I tried every permutation at this point.

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.

5 participants