From 75472ef2aca890c4635be5092b91b550c24145fd Mon Sep 17 00:00:00 2001 From: David <2889367+daveleroy@users.noreply.github.com> Date: Sat, 6 Mar 2021 20:47:09 -0800 Subject: [PATCH 01/17] Initial java configuration --- modules/adapters/__init__.py | 1 + modules/adapters/java.py | 96 ++++++++++++++++++++++++++++++++++++ start.py | 1 + 3 files changed, 98 insertions(+) create mode 100644 modules/adapters/java.py diff --git a/modules/adapters/__init__.py b/modules/adapters/__init__.py index 534cfec8..3fca0b24 100644 --- a/modules/adapters/__init__.py +++ b/modules/adapters/__init__.py @@ -6,6 +6,7 @@ from .go import Go from .php import PHP from .python import Python +from .java import Java from .node import Node from .chrome import Chrome diff --git a/modules/adapters/java.py b/modules/adapters/java.py new file mode 100644 index 00000000..61b263e7 --- /dev/null +++ b/modules/adapters/java.py @@ -0,0 +1,96 @@ +# Basic idea here is... +# When installing the adapter it would add the com.microsoft.java.debug.plugin-0.30.0.jar as a plugin to lsp-jdts +# When starting the adapter it will ask lsp-jdts to start the adapter by calling the command lsp_jdts_start_debugging exposed by lsp-jdts +# lsp-jdts will then call debugger_lsp_jdts_start_debugging_response after it has started the adapter +# Debugger will then connect to the given port and start debugging + +# see https://github.com/Microsoft/java-debug for how the lsp side needs to be setup it looks something like.... +# add the jar to the init options +# "initializationOptions": { +# "bundles": [ +# "path/to/microsoft/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-.jar" +# ] +# } +# command to start the debug session +# { +# "command": "vscode.java.startDebugSession" +# } + +from ..typecheck import * +from .. import core +from .import adapter + +import sublime +import sublime_plugin + +# window.run_command('debugger_lsp_jdts_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) +class DebuggerLspJdtsStartDebuggingResponseCommand(sublime_plugin.WindowCommand): + def run(self, id: int, port: Optional[int], error: Optional[str]): + future_port = Java.pending_adapters.get(id) + if not future_port: + print("Hmm... unable to find a future port for this id") + return + + if error: + future_port.set_exception(core.Error(error)) + return + + if not port: + future_port.set_exception(core.Error('Expected port or error from lsp-jdts')) + return + + future_port.set_result(port) + + +class Java(adapter.AdapterConfiguration): + pending_adapters: Dict[int, core.future] = {} + pending_adapters_current_id = 0 + + @property + def type(self): return 'java' + + async def start(self, log, configuration): + # 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_port = core.create_future() + + id = Java.pending_adapters_current_id + Java.pending_adapters_current_id += 1 + Java.pending_adapters[id] = future_port + + # ask lsp_jdts to start the debug adapter + # lsp_jdts will call debugger_lsp_jdts_start_debugging_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('lsp_jdts_start_debugging', { + 'id': id + }) + + port = await future_port + return adapter.SocketTransport(log, 'localhost', port) + + async def install(self, log): + url = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/vscjava/vsextensions/vscode-java-debug/latest/vspackage' + await adapter.vscode.install(self.type, url, log) + + install_path = adapter.vscode.install_path(self.type) + + # probably need to just look through this folder? this has a version # + plugin_jar_path = f'{install_path}/extension/server/com.microsoft.java.debug.plugin-0.30.0.jar' + + # TODO patch in the jar to lsp-jdts + # TODO restart lsp-jdts? + # probably need to allow running some stuff after downloading the extension but before it is marked as installed because this patching needs to succeed + + @property + def installed_version(self) -> Optional[str]: + return adapter.vscode.installed_version(self.type) + + @property + def configuration_snippets(self) -> Optional[list]: + return adapter.vscode.configuration_snippets(self.type) + + @property + def configuration_schema(self) -> Optional[dict]: + return adapter.vscode.configuration_schema(self.type) + diff --git a/start.py b/start.py index bcba2909..474f26dd 100644 --- a/start.py +++ b/start.py @@ -13,6 +13,7 @@ # import all the commands so that sublime sees them from .modules.commands import DebuggerCommand, DebuggerExecCommand +from .modules.adapters.java import DebuggerLspJdtsStartDebuggingResponseCommand from .modules.ui.input import DebuggerInputCommand from .modules.core.sublime import DebuggerAsyncTextCommand, DebuggerEventsListener From ca1cad66b47be26fa3e55ebe06201f701ee18ba1 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Mon, 8 Mar 2021 13:10:51 +0100 Subject: [PATCH 02/17] Implement java adapter --- modules/adapters/java.py | 30 ++++++++++++++++++++++-------- start.py | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 61b263e7..3a87f0c0 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -22,9 +22,10 @@ import sublime import sublime_plugin +import os -# window.run_command('debugger_lsp_jdts_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) -class DebuggerLspJdtsStartDebuggingResponseCommand(sublime_plugin.WindowCommand): +# window.run_command('debugger_lsp_jdtls_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) +class DebuggerLspJdtlsStartDebuggingResponseCommand(sublime_plugin.WindowCommand): def run(self, id: int, port: Optional[int], error: Optional[str]): future_port = Java.pending_adapters.get(id) if not future_port: @@ -59,10 +60,12 @@ async def start(self, log, configuration): Java.pending_adapters_current_id += 1 Java.pending_adapters[id] = future_port + sublime.status_message("Hello") + # ask lsp_jdts to start the debug adapter # lsp_jdts will call debugger_lsp_jdts_start_debugging_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('lsp_jdts_start_debugging', { + sublime.active_window().run_command('lsp_jdtls_start_debug_session', { 'id': id }) @@ -76,11 +79,22 @@ async def install(self, log): install_path = adapter.vscode.install_path(self.type) # probably need to just look through this folder? this has a version # - plugin_jar_path = f'{install_path}/extension/server/com.microsoft.java.debug.plugin-0.30.0.jar' - - # TODO patch in the jar to lsp-jdts - # TODO restart lsp-jdts? - # probably need to allow running some stuff after downloading the extension but before it is marked as installed because this patching needs to succeed + plugin_jar_path = os.path.join(f'{install_path}/extension/server', os.listdir(f'{install_path}/extension/server')[0]) + + settings = sublime.load_settings("LSP-jdtls.sublime-settings") + init_options = settings.get("initializationOptions", {}) + bundles = init_options.get("bundles", []) + + if plugin_jar_path not in bundles: + # Cleanup + for jar in bundles: + if "com.microsoft.java.debug.plugin" in jar: + bundles.remove(jar) + break + bundles += [plugin_jar_path] + init_options["bundles"] = bundles + settings.set("initializationOptions", init_options) + sublime.save_settings("LSP-jdtls.sublime-settings") @property def installed_version(self) -> Optional[str]: diff --git a/start.py b/start.py index 474f26dd..e2a7df19 100644 --- a/start.py +++ b/start.py @@ -13,7 +13,7 @@ # import all the commands so that sublime sees them from .modules.commands import DebuggerCommand, DebuggerExecCommand -from .modules.adapters.java import DebuggerLspJdtsStartDebuggingResponseCommand +from .modules.adapters.java import DebuggerLspJdtlsStartDebuggingResponseCommand from .modules.ui.input import DebuggerInputCommand from .modules.core.sublime import DebuggerAsyncTextCommand, DebuggerEventsListener From 2775766b578739ed2150d4cbf61445032f09edcb Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Mon, 8 Mar 2021 13:18:47 +0100 Subject: [PATCH 03/17] Remove debug output --- modules/adapters/java.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 3a87f0c0..74332c8c 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -60,8 +60,6 @@ async def start(self, log, configuration): Java.pending_adapters_current_id += 1 Java.pending_adapters[id] = future_port - sublime.status_message("Hello") - # ask lsp_jdts to start the debug adapter # lsp_jdts will call debugger_lsp_jdts_start_debugging_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. From d131bf19cd755601762f7494a35a532da61219f0 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Tue, 9 Mar 2021 13:21:57 +0100 Subject: [PATCH 04/17] Fix Exception on jdtls server Error parsing message: com.google.gson.JsonSyntaxException: Expected a com.google.gson.JsonObject but was com.google.gson.JsonNull --- modules/dap/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/dap/session.py b/modules/dap/session.py index 78e5850c..615b7550 100644 --- a/modules/dap/session.py +++ b/modules/dap/session.py @@ -669,7 +669,7 @@ async def on_initialized_event(self): if self.capabilities.supportsConfigurationDoneRequest: try: - await self.request('configurationDone', None) + await self.request('configurationDone', {}) except core.Error as e: self.error('there was an error in configuration done {}'.format(e)) @@ -735,7 +735,7 @@ def set_selected(self, thread: Thread, frame: Optional[dap.StackFrame]): # @NOTE threads_for_id will retain all threads for the entire session even if they are removed @core.schedule async def refresh_threads(self): - response = await self.request('threads', None) + response = await self.request('threads', {}) threads = array_from_json(dap.Thread.from_json, response['threads']) self.threads.clear() From 99d72ec3222eee7208ad50c67c55a1c101592d73 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Wed, 10 Mar 2021 13:11:39 +0100 Subject: [PATCH 05/17] Resolve classPaths and mainClass --- modules/adapters/java.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 74332c8c..aca6ebc0 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -26,21 +26,13 @@ # window.run_command('debugger_lsp_jdtls_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) class DebuggerLspJdtlsStartDebuggingResponseCommand(sublime_plugin.WindowCommand): - def run(self, id: int, port: Optional[int], error: Optional[str]): - future_port = Java.pending_adapters.get(id) - if not future_port: + def run(self, **args): + future = Java.pending_adapters.get(args["id"]) + if not future: print("Hmm... unable to find a future port for this id") return - if error: - future_port.set_exception(core.Error(error)) - return - - if not port: - future_port.set_exception(core.Error('Expected port or error from lsp-jdts')) - return - - future_port.set_result(port) + future.set_result(args) class Java(adapter.AdapterConfiguration): @@ -54,11 +46,11 @@ async def start(self, log, configuration): # 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_port = core.create_future() + future = core.create_future() id = Java.pending_adapters_current_id Java.pending_adapters_current_id += 1 - Java.pending_adapters[id] = future_port + Java.pending_adapters[id] = future # ask lsp_jdts to start the debug adapter # lsp_jdts will call debugger_lsp_jdts_start_debugging_response with the id it was given and a port to connect to the adapter with or an error @@ -67,8 +59,17 @@ async def start(self, log, configuration): 'id': id }) - port = await future_port - return adapter.SocketTransport(log, 'localhost', port) + args = await future + if 'cwd' not in configuration: + configuration['cwd'], _ = os.path.split(sublime.active_window().project_file_name()) + if 'mainClass' not in configuration: + configuration['mainClass'] = args['mainClass'] + if 'classPaths' not in configuration: + configuration['classPaths'] = args['classPaths'] + if 'console' not in configuration: + configuration['console'] = 'internalConsole' + + return adapter.SocketTransport(log, 'localhost', args["port"]) async def install(self, log): url = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/vscjava/vsextensions/vscode-java-debug/latest/vspackage' From b126fc4be74fbaafde9fa2e5c97c23e0a5305cf8 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Wed, 10 Mar 2021 14:12:59 +0100 Subject: [PATCH 06/17] Show errors --- modules/adapters/java.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index aca6ebc0..794679b9 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -31,6 +31,9 @@ def run(self, **args): if not future: print("Hmm... unable to find a future port for this id") return + if args["error"]: + future.set_exception(core.Error(args["error"])) + return future.set_result(args) @@ -62,7 +65,7 @@ async def start(self, log, configuration): args = await future if 'cwd' not in configuration: configuration['cwd'], _ = os.path.split(sublime.active_window().project_file_name()) - if 'mainClass' not in configuration: + if 'mainClass' not in configuration or not configuration['mainClass']: configuration['mainClass'] = args['mainClass'] if 'classPaths' not in configuration: configuration['classPaths'] = args['classPaths'] From c482ce78ee940f5152287004647382b302616256 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Wed, 10 Mar 2021 15:53:04 +0100 Subject: [PATCH 07/17] Fix modulePaths and incompatible class version --- modules/adapters/java.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 794679b9..88d90f71 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -69,8 +69,15 @@ async def start(self, log, configuration): configuration['mainClass'] = args['mainClass'] if 'classPaths' not in configuration: configuration['classPaths'] = args['classPaths'] + if 'modulePaths' not in configuration: + configuration['modulePaths'] = args['modulePaths'] if 'console' not in configuration: configuration['console'] = 'internalConsole' + if args["enablePreview"]: + if 'vmArgs' in configuration: + configuration['vmArgs'] += " --enable-preview" + else: + configuration['vmArgs'] = "--enable-preview" return adapter.SocketTransport(log, 'localhost', args["port"]) From a0fc030f12fdc4d1e5a34318ff7030457765fae1 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Wed, 10 Mar 2021 18:32:27 +0100 Subject: [PATCH 08/17] Allow failure when resolving mainClass/classPaths if set in configuration --- modules/adapters/java.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 88d90f71..2b85e7cf 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -31,9 +31,6 @@ def run(self, **args): if not future: print("Hmm... unable to find a future port for this id") return - if args["error"]: - future.set_exception(core.Error(args["error"])) - return future.set_result(args) @@ -66,8 +63,12 @@ async def start(self, log, configuration): if 'cwd' not in configuration: configuration['cwd'], _ = os.path.split(sublime.active_window().project_file_name()) if 'mainClass' not in configuration or not configuration['mainClass']: + if 'mainClass' not in args: + raise core.Error(args["error"]) configuration['mainClass'] = args['mainClass'] if 'classPaths' not in configuration: + if 'classPaths' not in args: + raise core.Error(args["error"]) configuration['classPaths'] = args['classPaths'] if 'modulePaths' not in configuration: configuration['modulePaths'] = args['modulePaths'] From 48056186ff8928b57a44de1eff4adf9b9f7de979 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Thu, 11 Mar 2021 00:08:01 +0100 Subject: [PATCH 09/17] Fix for debug-java out-of-specification response --- modules/dap/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dap/session.py b/modules/dap/session.py index 615b7550..a6b465e0 100644 --- a/modules/dap/session.py +++ b/modules/dap/session.py @@ -736,7 +736,7 @@ def set_selected(self, thread: Thread, frame: Optional[dap.StackFrame]): @core.schedule async def refresh_threads(self): response = await self.request('threads', {}) - threads = array_from_json(dap.Thread.from_json, response['threads']) + threads = array_from_json(dap.Thread.from_json, response.get('threads', [])) self.threads.clear() for thread in threads: From 846c839fcb8058710e1b1e7c29ce69d195d2a85c Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Mon, 14 Jun 2021 18:23:44 +0200 Subject: [PATCH 10/17] Update Java adapter to new API --- modules/adapters/java.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 2b85e7cf..5fa6c51b 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -36,17 +36,17 @@ def run(self, **args): class Java(adapter.AdapterConfiguration): - pending_adapters: Dict[int, core.future] = {} + pending_adapters: Dict[int, core.Future] = {} pending_adapters_current_id = 0 - @property - def type(self): return 'java' + type = 'java' + docs = 'https://github.com/redhat-developer/vscode-java/blob/master/README.md' async def start(self, log, configuration): # 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.create_future() + future = core.Future() id = Java.pending_adapters_current_id Java.pending_adapters_current_id += 1 @@ -83,7 +83,7 @@ async def start(self, log, configuration): return adapter.SocketTransport(log, 'localhost', args["port"]) async def install(self, log): - url = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/vscjava/vsextensions/vscode-java-debug/latest/vspackage' + url = await adapter.openvsx.latest_release_vsix('vscjava', 'vscode-java-debug') await adapter.vscode.install(self.type, url, log) install_path = adapter.vscode.install_path(self.type) @@ -106,6 +106,9 @@ async def install(self, log): settings.set("initializationOptions", init_options) sublime.save_settings("LSP-jdtls.sublime-settings") + async def installed_status(self, log): + return await adapter.openvsx.installed_status('vscjava', 'vscode-java-debug', self.installed_version) + @property def installed_version(self) -> Optional[str]: return adapter.vscode.installed_version(self.type) From dbe71820cdbaaf05851475caa9a756deac1f6906 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Fri, 16 Sep 2022 18:19:18 +0200 Subject: [PATCH 11/17] Update Java adapter to new API --- modules/adapters/java.py | 112 +++++++++++++++-------------------- modules/source_navigation.py | 38 ++++++++++-- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 5fa6c51b..b9817f40 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -4,28 +4,25 @@ # lsp-jdts will then call debugger_lsp_jdts_start_debugging_response after it has started the adapter # Debugger will then connect to the given port and start debugging -# see https://github.com/Microsoft/java-debug for how the lsp side needs to be setup it looks something like.... -# add the jar to the init options -# "initializationOptions": { -# "bundles": [ -# "path/to/microsoft/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-.jar" -# ] -# } +# see https://github.com/Microsoft/java-debug for how the lsp side needs to be setup # command to start the debug session -# { -# "command": "vscode.java.startDebugSession" -# } +# { +# "command": "vscode.java.startDebugSession" +# } -from ..typecheck import * -from .. import core -from .import adapter +from ..typecheck import Optional, Dict +from ..import core +from ..import dap +from .import util import sublime import sublime_plugin import os -# window.run_command('debugger_lsp_jdtls_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) + class DebuggerLspJdtlsStartDebuggingResponseCommand(sublime_plugin.WindowCommand): + # window.run_command('debugger_lsp_jdtls_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) + def run(self, **args): future = Java.pending_adapters.get(args["id"]) if not future: @@ -35,17 +32,21 @@ def run(self, **args): future.set_result(args) -class Java(adapter.AdapterConfiguration): +class Java(dap.AdapterConfiguration): pending_adapters: Dict[int, core.Future] = {} pending_adapters_current_id = 0 - type = 'java' - docs = 'https://github.com/redhat-developer/vscode-java/blob/master/README.md' + type = "java" + docs = "https://github.com/redhat-developer/vscode-java/blob/master/README.md" async def start(self, log, configuration): + pc_settings = sublime.load_settings("Package Control.sublime-settings") + installed_packages = pc_settings.get("installed_packages", []) + + if "LSP-jdtls" not in installed_packages or "LSP" not in installed_packages: + raise core.Error("LSP and LSP-jdtls required to debug Java!") + # 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() id = Java.pending_adapters_current_id @@ -55,69 +56,50 @@ async def start(self, log, configuration): # ask lsp_jdts to start the debug adapter # lsp_jdts will call debugger_lsp_jdts_start_debugging_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('lsp_jdtls_start_debug_session', { - 'id': id - }) + sublime.active_window().run_command("lsp_jdtls_start_debug_session", {"id": id}) args = await future - if 'cwd' not in configuration: - configuration['cwd'], _ = os.path.split(sublime.active_window().project_file_name()) - if 'mainClass' not in configuration or not configuration['mainClass']: - if 'mainClass' not in args: + if "cwd" not in configuration: + configuration["cwd"], _ = os.path.split( + sublime.active_window().project_file_name() + ) + if "mainClass" not in configuration or not configuration["mainClass"]: + if "mainClass" not in args: raise core.Error(args["error"]) - configuration['mainClass'] = args['mainClass'] - if 'classPaths' not in configuration: - if 'classPaths' not in args: + configuration["mainClass"] = args["mainClass"] + if "classPaths" not in configuration: + if "classPaths" not in args: raise core.Error(args["error"]) - configuration['classPaths'] = args['classPaths'] - if 'modulePaths' not in configuration: - configuration['modulePaths'] = args['modulePaths'] - if 'console' not in configuration: - configuration['console'] = 'internalConsole' + configuration["classPaths"] = args["classPaths"] + if "modulePaths" not in configuration: + configuration["modulePaths"] = args["modulePaths"] + if "console" not in configuration: + configuration["console"] = "internalConsole" if args["enablePreview"]: - if 'vmArgs' in configuration: - configuration['vmArgs'] += " --enable-preview" + if "vmArgs" in configuration: + configuration["vmArgs"] += " --enable-preview" else: - configuration['vmArgs'] = "--enable-preview" + configuration["vmArgs"] = "--enable-preview" - return adapter.SocketTransport(log, 'localhost', args["port"]) + return dap.SocketTransport(log, "localhost", args["port"]) async def install(self, log): - url = await adapter.openvsx.latest_release_vsix('vscjava', 'vscode-java-debug') - await adapter.vscode.install(self.type, url, log) - - install_path = adapter.vscode.install_path(self.type) - - # probably need to just look through this folder? this has a version # - plugin_jar_path = os.path.join(f'{install_path}/extension/server', os.listdir(f'{install_path}/extension/server')[0]) - - settings = sublime.load_settings("LSP-jdtls.sublime-settings") - init_options = settings.get("initializationOptions", {}) - bundles = init_options.get("bundles", []) - - if plugin_jar_path not in bundles: - # Cleanup - for jar in bundles: - if "com.microsoft.java.debug.plugin" in jar: - bundles.remove(jar) - break - bundles += [plugin_jar_path] - init_options["bundles"] = bundles - settings.set("initializationOptions", init_options) - sublime.save_settings("LSP-jdtls.sublime-settings") + url = await util.openvsx.latest_release_vsix("vscjava", "vscode-java-debug") + await util.vscode.install(self.type, url, log) async def installed_status(self, log): - return await adapter.openvsx.installed_status('vscjava', 'vscode-java-debug', self.installed_version) + return await util.openvsx.installed_status( + "vscjava", "vscode-java-debug", self.installed_version + ) @property def installed_version(self) -> Optional[str]: - return adapter.vscode.installed_version(self.type) + return util.vscode.installed_version(self.type) @property def configuration_snippets(self) -> Optional[list]: - return adapter.vscode.configuration_snippets(self.type) + return util.vscode.configuration_snippets(self.type) @property def configuration_schema(self) -> Optional[dict]: - return adapter.vscode.configuration_schema(self.type) - + return util.vscode.configuration_schema(self.type) diff --git a/modules/source_navigation.py b/modules/source_navigation.py index a9162060..f4a3fb52 100644 --- a/modules/source_navigation.py +++ b/modules/source_navigation.py @@ -1,4 +1,5 @@ from __future__ import annotations + from .typecheck import * from .import core @@ -97,7 +98,6 @@ def clear_generated_view(self): self.generated_view = None async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool = False) -> sublime.View: - # if we aren't going to reuse the previous generated view throw away any generated view if not source.source.sourceReference: self.clear_generated_view() @@ -121,7 +121,7 @@ async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool self.generated_view = view view.set_name(source.source.name or "") view.set_read_only(False) - + syntax = syntax_name_for_mime_type.get(mime_type, 'text.plain') view.assign_syntax(sublime.find_syntax_by_scope(syntax)[0]) @@ -131,11 +131,39 @@ async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool view.set_read_only(True) view.set_scratch(True) elif source.source.path: - view = await core.sublime_open_file_async(self.project.window, source.source.path, group=0) + if source.source.path.startswith("jdt:"): + lsp_args = { + "location": { + "uri": source.source.path, + "range": { + "start": { + "line": line, + "character": column + }, + "end": { + "line": line, + "character": column + } + } + }, + "session_name": "jdtls" + } + view = self.project.window.active_view() + assert view is not None + view.run_command("lsp_open_location", lsp_args) + # very dirty hack + # todo: wait for the view to appear without sleep + await core.sleep(1) + for v in self.project.window.views(): + if v.name() == source.source.path: + view = v + view.set_name(source.source.name or "") + view.set_read_only(True) + break + else: + view = await core.sublime_open_file_async(self.project.window, source.source.path, group=0) else: raise core.Error('source has no reference or path') - show_line(view, line, column, move_cursor) - return view From ef9bbec8a160628a3c1e318a82e34a512678fa9c Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Sat, 17 Sep 2022 12:05:22 +0200 Subject: [PATCH 12/17] Java: Move LSP config logic into debugger --- modules/adapters/java.py | 137 +++++++++++++++++++++++++---------- modules/source_navigation.py | 51 ++++++------- start.py | 2 +- 3 files changed, 123 insertions(+), 67 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index b9817f40..6a187d50 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -10,7 +10,7 @@ # "command": "vscode.java.startDebugSession" # } -from ..typecheck import Optional, Dict +from ..typecheck import Optional, Dict, Any from ..import core from ..import dap from .import util @@ -18,70 +18,49 @@ import sublime import sublime_plugin import os - - -class DebuggerLspJdtlsStartDebuggingResponseCommand(sublime_plugin.WindowCommand): - # window.run_command('debugger_lsp_jdtls_start_debugging_response', {'id': 1, 'port': 12345, 'error': None}) - - def run(self, **args): - future = Java.pending_adapters.get(args["id"]) - if not future: - print("Hmm... unable to find a future port for this id") - return - - future.set_result(args) +import json class Java(dap.AdapterConfiguration): - pending_adapters: Dict[int, core.Future] = {} - pending_adapters_current_id = 0 + jdtls_bridge: Dict[int, core.Future] = {} + jdtls_bridge_current_id = 0 type = "java" docs = "https://github.com/redhat-developer/vscode-java/blob/master/README.md" async def start(self, log, configuration): + # Make sure LSP and LSP-JDTLS are installed pc_settings = sublime.load_settings("Package Control.sublime-settings") installed_packages = pc_settings.get("installed_packages", []) - if "LSP-jdtls" not in installed_packages or "LSP" not in installed_packages: raise core.Error("LSP and LSP-jdtls required to debug Java!") - # probably need to add some sort of timeout - future = core.Future() - - id = Java.pending_adapters_current_id - Java.pending_adapters_current_id += 1 - Java.pending_adapters[id] = future - - # ask lsp_jdts to start the debug adapter - # lsp_jdts will call debugger_lsp_jdts_start_debugging_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("lsp_jdtls_start_debug_session", {"id": id}) + # Get configuration from LSP + lsp_config = await self.get_configuration_from_lsp() - args = await future + # Configure debugger if "cwd" not in configuration: configuration["cwd"], _ = os.path.split( sublime.active_window().project_file_name() ) if "mainClass" not in configuration or not configuration["mainClass"]: - if "mainClass" not in args: - raise core.Error(args["error"]) - configuration["mainClass"] = args["mainClass"] + configuration["mainClass"] = lsp_config["mainClass"] if "classPaths" not in configuration: - if "classPaths" not in args: - raise core.Error(args["error"]) - configuration["classPaths"] = args["classPaths"] + configuration["classPaths"] = lsp_config["classPaths"] if "modulePaths" not in configuration: - configuration["modulePaths"] = args["modulePaths"] + configuration["modulePaths"] = lsp_config["modulePaths"] if "console" not in configuration: configuration["console"] = "internalConsole" - if args["enablePreview"]: + if lsp_config["enablePreview"]: if "vmArgs" in configuration: configuration["vmArgs"] += " --enable-preview" else: configuration["vmArgs"] = "--enable-preview" - return dap.SocketTransport(log, "localhost", args["port"]) + # Start debugging session on the LSP side + port = await self.lsp_execute_command("vscode.java.startDebugSession") + + return dap.SocketTransport(log, "localhost", port) async def install(self, log): url = await util.openvsx.latest_release_vsix("vscjava", "vscode-java-debug") @@ -103,3 +82,87 @@ def configuration_snippets(self) -> Optional[list]: @property def configuration_schema(self) -> Optional[dict]: return util.vscode.configuration_schema(self.type) + + async def get_class_content_for_uri(self, uri): + return await self.lsp_request("java/classFileContents", {"uri": uri}) + + async def get_configuration_from_lsp(self) -> dict: + lsp_config = {} + + mainclass_resp = await self.lsp_execute_command("vscode.java.resolveMainClass") + if not mainclass_resp or "mainClass" not in mainclass_resp[0]: + raise core.Error("Failed to resolve main class") + else: + lsp_config["mainClass"] = mainclass_resp[0]["mainClass"] + lsp_config["projectName"] = mainclass_resp[0].get("projectName", "") + + classpath_response = await self.lsp_execute_command( + "vscode.java.resolveClasspath", [lsp_config["mainClass"], lsp_config["projectName"]] + ) + if not classpath_response[0] and not classpath_response[1]: + raise core.Error("Failed to resolve classpaths/modulepaths") + else: + lsp_config["modulePaths"] = classpath_response[0] + lsp_config["classPaths"] = classpath_response[1] + + # See https://github.com/microsoft/vscode-java-debug/blob/b2a48319952b1af8a4a328fc95d2891de947df94/src/configurationProvider.ts#L297 + lsp_config["enablePreview"] = await self.lsp_execute_command( + "vscode.java.checkProjectSettings", + [ + json.dumps( + { + "className": lsp_config["mainClass"], + "projectName": lsp_config["projectName"], + "inheritedOptions": True, + "expectedOptions": { + "org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures": "enabled" + }, + } + ) + ] + ) + + return lsp_config + + async def lsp_execute_command(self, command, arguments=None): + request_params = { "command": command } + if arguments: + request_params["arguments"] = arguments + return await self.lsp_request("workspace/executeCommand", request_params) + + async def lsp_request(self, method, params) -> Any: + """ + Returns the response or raises an exception. + """ + + # probably need to add some sort of timeout + future = core.Future() + + id = Java.jdtls_bridge_current_id + Java.jdtls_bridge_current_id += 1 + Java.jdtls_bridge[id] = future + + # Send a request to JDTLS. + # NOTE: the active window might not match the debugger window but generally will + # TODO: a way to get the actual window. + sublime.active_window().run_command( + "debugger_jdtls_bridge_request", + {"id": id, "callback_command": "debugger_jdtls_bridge_response", "method": method, "params": params} + ) + + command_response = await future + + del Java.jdtls_bridge[id] + + if command_response["error"]: + raise core.Error(command_response["error"]) + return command_response["resp"] + + +class DebuggerJdtlsBridgeResponseCommand(sublime_plugin.WindowCommand): + def run(self, **args): + future = Java.jdtls_bridge.get(args["id"]) + if not future: + print("Unable to find a future for this id") + return + future.set_result(args) diff --git a/modules/source_navigation.py b/modules/source_navigation.py index f4a3fb52..e3318bc1 100644 --- a/modules/source_navigation.py +++ b/modules/source_navigation.py @@ -1,5 +1,6 @@ from __future__ import annotations + from .typecheck import * from .import core @@ -98,6 +99,9 @@ def clear_generated_view(self): self.generated_view = None async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool = False) -> sublime.View: + # TODO: Prevent circular import using interface + from .adapters.java import Java + # if we aren't going to reuse the previous generated view throw away any generated view if not source.source.sourceReference: self.clear_generated_view() @@ -131,35 +135,24 @@ async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool view.set_read_only(True) view.set_scratch(True) elif source.source.path: - if source.source.path.startswith("jdt:"): - lsp_args = { - "location": { - "uri": source.source.path, - "range": { - "start": { - "line": line, - "character": column - }, - "end": { - "line": line, - "character": column - } - } - }, - "session_name": "jdtls" - } - view = self.project.window.active_view() - assert view is not None - view.run_command("lsp_open_location", lsp_args) - # very dirty hack - # todo: wait for the view to appear without sleep - await core.sleep(1) - for v in self.project.window.views(): - if v.name() == source.source.path: - view = v - view.set_name(source.source.name or "") - view.set_read_only(True) - break + if source.source.path.startswith("jdt:") and self.debugger.session and isinstance(self.debugger.session.adapter_configuration, Java): + # the generated view was closed (no buffer) throw it away + if self.generated_view and not self.generated_view.buffer_id(): + self.clear_generated_view() + + view = self.generated_view or self.project.window.new_file() + self.project.window.set_view_index(view, 0, len(self.project.window.views_in_group(0))) + self.generated_view = view + view.set_name(source.source.name or "") + view.set_read_only(False) + + view.assign_syntax(sublime.find_syntax_by_scope("source.java")[0]) + content = await self.debugger.session.adapter_configuration.get_class_content_for_uri(source.source.path) + + replace_contents(view, content) + + view.set_read_only(True) + view.set_scratch(True) else: view = await core.sublime_open_file_async(self.project.window, source.source.path, group=0) else: diff --git a/start.py b/start.py index 2e082243..6cbcb693 100644 --- a/start.py +++ b/start.py @@ -15,7 +15,7 @@ # import all the commands so that sublime sees them from .modules.command import CommandsRegistry, DebuggerExecCommand, DebuggerCommand, DebuggerInputCommand -from .modules.adapters.java import DebuggerLspJdtlsStartDebuggingResponseCommand +from .modules.adapters.java import DebuggerJdtlsBridgeResponseCommand from .modules.core.sublime import DebuggerAsyncTextCommand, DebuggerEventsListener from .modules.debugger_output_panel import DebuggerConsoleListener From aac31cb75a0fd55836bf89a4e9ea8f58310d66bb Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Sat, 17 Sep 2022 12:46:40 +0200 Subject: [PATCH 13/17] Java: Move source navigation special case to adapter --- modules/adapters/java.py | 8 ++++++- modules/dap/configuration.py | 11 ++++++++- modules/source_navigation.py | 46 +++++++++++++----------------------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index 6a187d50..d999d307 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -10,7 +10,7 @@ # "command": "vscode.java.startDebugSession" # } -from ..typecheck import Optional, Dict, Any +from ..typecheck import Optional, Dict, Any, Tuple from ..import core from ..import dap from .import util @@ -83,6 +83,12 @@ def configuration_snippets(self) -> Optional[list]: def configuration_schema(self) -> Optional[dict]: return util.vscode.configuration_schema(self.type) + async def on_navigate_to_source(self, source: dap.SourceLocation) -> Optional[Tuple[str, str]]: + if not source.source.path or not source.source.path.startswith("jdt:"): + return None + content = await self.get_class_content_for_uri(source.source.path) + return content, "text/java" + async def get_class_content_for_uri(self, uri): return await self.lsp_request("java/classFileContents", {"uri": uri}) diff --git a/modules/dap/configuration.py b/modules/dap/configuration.py index a1a02de1..d82e123c 100644 --- a/modules/dap/configuration.py +++ b/modules/dap/configuration.py @@ -2,6 +2,7 @@ from ..typecheck import * from ..import core +from ..import dap from .transport import Transport import sublime @@ -77,6 +78,14 @@ def settings(self, debugger: Debugger) -> list[Any]: def ui(self, debugger: Debugger) -> Any|None: ... + async def on_navigate_to_source(self, source: dap.SourceLocation) -> Optional[Tuple[str, str]]: + """ + Allows the adapter to supply content when navigating to source. + Returns: None to keep the default behavior, else a tuple (content, mime_type) + """ + return None + + class Configuration(Dict[str, Any]): def __init__(self, name: str, index: int, type: str, request: str, all: dict[str, Any]): super().__init__(all) @@ -161,4 +170,4 @@ def _expand_variables_and_platform(json: dict[str, Any], variables: dict[str, st if variables := variables: json = sublime.expand_variables(json, variables) - return json \ No newline at end of file + return json diff --git a/modules/source_navigation.py b/modules/source_navigation.py index e3318bc1..484c7df3 100644 --- a/modules/source_navigation.py +++ b/modules/source_navigation.py @@ -17,6 +17,7 @@ syntax_name_for_mime_type: dict[str|None, str] = { 'text/plain': 'text.plain', 'text/javascript': 'source.js', + 'text/java': 'source.java', 'text/x-lldb.disassembly': 'source.disassembly', } @@ -99,22 +100,27 @@ def clear_generated_view(self): self.generated_view = None async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool = False) -> sublime.View: - # TODO: Prevent circular import using interface - from .adapters.java import Java + # Check if adapter want to provide content + if self.debugger.session: + adapter_content = await self.debugger.session.adapter_configuration.on_navigate_to_source(source) + else: + adapter_content = None # if we aren't going to reuse the previous generated view throw away any generated view - if not source.source.sourceReference: + if adapter_content or source.source.sourceReference: self.clear_generated_view() line = (source.line or 1) - 1 column = (source.column or 1) - 1 - if source.source.sourceReference: - session = self.debugger.session - if not session: - raise core.Error('No Active Debug Session') - - content, mime_type = await session.get_source(source.source) + if adapter_content or source.source.sourceReference: + if adapter_content: + content, mime_type = adapter_content + else: + session = self.debugger.session + if not session: + raise core.Error('No Active Debug Session') + content, mime_type = await session.get_source(source.source) # the generated view was closed (no buffer) throw it away if self.generated_view and not self.generated_view.buffer_id(): @@ -126,7 +132,6 @@ async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool view.set_name(source.source.name or "") view.set_read_only(False) - syntax = syntax_name_for_mime_type.get(mime_type, 'text.plain') view.assign_syntax(sublime.find_syntax_by_scope(syntax)[0]) @@ -135,26 +140,7 @@ async def navigate_to_source(self, source: dap.SourceLocation, move_cursor: bool view.set_read_only(True) view.set_scratch(True) elif source.source.path: - if source.source.path.startswith("jdt:") and self.debugger.session and isinstance(self.debugger.session.adapter_configuration, Java): - # the generated view was closed (no buffer) throw it away - if self.generated_view and not self.generated_view.buffer_id(): - self.clear_generated_view() - - view = self.generated_view or self.project.window.new_file() - self.project.window.set_view_index(view, 0, len(self.project.window.views_in_group(0))) - self.generated_view = view - view.set_name(source.source.name or "") - view.set_read_only(False) - - view.assign_syntax(sublime.find_syntax_by_scope("source.java")[0]) - content = await self.debugger.session.adapter_configuration.get_class_content_for_uri(source.source.path) - - replace_contents(view, content) - - view.set_read_only(True) - view.set_scratch(True) - else: - view = await core.sublime_open_file_async(self.project.window, source.source.path, group=0) + view = await core.sublime_open_file_async(self.project.window, source.source.path, group=0) else: raise core.Error('source has no reference or path') From 71fabb5fb6d5f19ee1a0c64ed0bb294679b946ab Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Sat, 17 Sep 2022 12:54:12 +0200 Subject: [PATCH 14/17] Update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1df2bfaf..7cb30b68 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ This project comes with some pre-configured debuggers (They can be installed usi ##### PHP - See https://github.com/felixfbecker/vscode-php-debug +##### Java +- Requires [LSP](https://packagecontrol.io/packages/LSP) and [LSP-jdtls](https://packagecontrol.io/packages/LSP-jdtls) +- See https://github.com/redhat-developer/vscode-java + ## Setup - Open the debug panel - from the command palette `Debugger: Open` From 83650c26cc1ebc28b63e5fb379f539e52d0b0098 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Sat, 17 Sep 2022 14:02:54 +0200 Subject: [PATCH 15/17] Java: Format --- modules/adapters/java.py | 116 +++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index d999d307..cb177e47 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -7,7 +7,7 @@ # see https://github.com/Microsoft/java-debug for how the lsp side needs to be setup # command to start the debug session # { -# "command": "vscode.java.startDebugSession" +# 'command': 'vscode.java.startDebugSession' # } from ..typecheck import Optional, Dict, Any, Tuple @@ -25,51 +25,47 @@ class Java(dap.AdapterConfiguration): jdtls_bridge: Dict[int, core.Future] = {} jdtls_bridge_current_id = 0 - type = "java" - docs = "https://github.com/redhat-developer/vscode-java/blob/master/README.md" + type = 'java' + docs = 'https://github.com/redhat-developer/vscode-java/blob/master/README.md' async def start(self, log, configuration): # Make sure LSP and LSP-JDTLS are installed - pc_settings = sublime.load_settings("Package Control.sublime-settings") - installed_packages = pc_settings.get("installed_packages", []) - if "LSP-jdtls" not in installed_packages or "LSP" not in installed_packages: - raise core.Error("LSP and LSP-jdtls required to debug Java!") + pc_settings = sublime.load_settings('Package Control.sublime-settings') + installed_packages = pc_settings.get('installed_packages', []) + if 'LSP-jdtls' not in installed_packages or 'LSP' not in installed_packages: + raise core.Error('LSP and LSP-jdtls required to debug Java!') # Get configuration from LSP lsp_config = await self.get_configuration_from_lsp() # Configure debugger - if "cwd" not in configuration: - configuration["cwd"], _ = os.path.split( - sublime.active_window().project_file_name() - ) - if "mainClass" not in configuration or not configuration["mainClass"]: - configuration["mainClass"] = lsp_config["mainClass"] - if "classPaths" not in configuration: - configuration["classPaths"] = lsp_config["classPaths"] - if "modulePaths" not in configuration: - configuration["modulePaths"] = lsp_config["modulePaths"] - if "console" not in configuration: - configuration["console"] = "internalConsole" - if lsp_config["enablePreview"]: - if "vmArgs" in configuration: - configuration["vmArgs"] += " --enable-preview" + if 'cwd' not in configuration: + configuration['cwd'], _ = os.path.split(sublime.active_window().project_file_name()) + if 'mainClass' not in configuration or not configuration['mainClass']: + configuration['mainClass'] = lsp_config['mainClass'] + if 'classPaths' not in configuration: + configuration['classPaths'] = lsp_config['classPaths'] + if 'modulePaths' not in configuration: + configuration['modulePaths'] = lsp_config['modulePaths'] + if 'console' not in configuration: + configuration['console'] = 'internalConsole' + if lsp_config['enablePreview']: + if 'vmArgs' in configuration: + configuration['vmArgs'] += ' --enable-preview' else: - configuration["vmArgs"] = "--enable-preview" + configuration['vmArgs'] = '--enable-preview' # Start debugging session on the LSP side - port = await self.lsp_execute_command("vscode.java.startDebugSession") + port = await self.lsp_execute_command('vscode.java.startDebugSession') - return dap.SocketTransport(log, "localhost", port) + return dap.SocketTransport(log, 'localhost', port) async def install(self, log): - url = await util.openvsx.latest_release_vsix("vscjava", "vscode-java-debug") + url = await util.openvsx.latest_release_vsix('vscjava', 'vscode-java-debug') await util.vscode.install(self.type, url, log) async def installed_status(self, log): - return await util.openvsx.installed_status( - "vscjava", "vscode-java-debug", self.installed_version - ) + return await util.openvsx.installed_status('vscjava', 'vscode-java-debug', self.installed_version) @property def installed_version(self) -> Optional[str]: @@ -84,44 +80,44 @@ def configuration_schema(self) -> Optional[dict]: return util.vscode.configuration_schema(self.type) async def on_navigate_to_source(self, source: dap.SourceLocation) -> Optional[Tuple[str, str]]: - if not source.source.path or not source.source.path.startswith("jdt:"): + if not source.source.path or not source.source.path.startswith('jdt:'): return None content = await self.get_class_content_for_uri(source.source.path) - return content, "text/java" + return content, 'text/java' async def get_class_content_for_uri(self, uri): - return await self.lsp_request("java/classFileContents", {"uri": uri}) + return await self.lsp_request('java/classFileContents', {'uri': uri}) async def get_configuration_from_lsp(self) -> dict: lsp_config = {} - mainclass_resp = await self.lsp_execute_command("vscode.java.resolveMainClass") - if not mainclass_resp or "mainClass" not in mainclass_resp[0]: - raise core.Error("Failed to resolve main class") + mainclass_resp = await self.lsp_execute_command('vscode.java.resolveMainClass') + if not mainclass_resp or 'mainClass' not in mainclass_resp[0]: + raise core.Error('Failed to resolve main class') else: - lsp_config["mainClass"] = mainclass_resp[0]["mainClass"] - lsp_config["projectName"] = mainclass_resp[0].get("projectName", "") + lsp_config['mainClass'] = mainclass_resp[0]['mainClass'] + lsp_config['projectName'] = mainclass_resp[0].get('projectName', '') classpath_response = await self.lsp_execute_command( - "vscode.java.resolveClasspath", [lsp_config["mainClass"], lsp_config["projectName"]] + 'vscode.java.resolveClasspath', [lsp_config['mainClass'], lsp_config['projectName']] ) if not classpath_response[0] and not classpath_response[1]: - raise core.Error("Failed to resolve classpaths/modulepaths") + raise core.Error('Failed to resolve classpaths/modulepaths') else: - lsp_config["modulePaths"] = classpath_response[0] - lsp_config["classPaths"] = classpath_response[1] + lsp_config['modulePaths'] = classpath_response[0] + lsp_config['classPaths'] = classpath_response[1] # See https://github.com/microsoft/vscode-java-debug/blob/b2a48319952b1af8a4a328fc95d2891de947df94/src/configurationProvider.ts#L297 - lsp_config["enablePreview"] = await self.lsp_execute_command( - "vscode.java.checkProjectSettings", + lsp_config['enablePreview'] = await self.lsp_execute_command( + 'vscode.java.checkProjectSettings', [ json.dumps( { - "className": lsp_config["mainClass"], - "projectName": lsp_config["projectName"], - "inheritedOptions": True, - "expectedOptions": { - "org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures": "enabled" + 'className': lsp_config['mainClass'], + 'projectName': lsp_config['projectName'], + 'inheritedOptions': True, + 'expectedOptions': { + 'org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures': 'enabled' }, } ) @@ -131,15 +127,15 @@ async def get_configuration_from_lsp(self) -> dict: return lsp_config async def lsp_execute_command(self, command, arguments=None): - request_params = { "command": command } + request_params = { 'command': command } if arguments: - request_params["arguments"] = arguments - return await self.lsp_request("workspace/executeCommand", request_params) + request_params['arguments'] = arguments + return await self.lsp_request('workspace/executeCommand', request_params) async def lsp_request(self, method, params) -> Any: - """ + ''' Returns the response or raises an exception. - """ + ''' # probably need to add some sort of timeout future = core.Future() @@ -152,23 +148,23 @@ async def lsp_request(self, method, params) -> Any: # NOTE: the active window might not match the debugger window but generally will # TODO: a way to get the actual window. sublime.active_window().run_command( - "debugger_jdtls_bridge_request", - {"id": id, "callback_command": "debugger_jdtls_bridge_response", "method": method, "params": params} + 'debugger_jdtls_bridge_request', + {'id': id, 'callback_command': 'debugger_jdtls_bridge_response', 'method': method, 'params': params} ) command_response = await future del Java.jdtls_bridge[id] - if command_response["error"]: - raise core.Error(command_response["error"]) - return command_response["resp"] + if command_response['error']: + raise core.Error(command_response['error']) + return command_response['resp'] class DebuggerJdtlsBridgeResponseCommand(sublime_plugin.WindowCommand): def run(self, **args): - future = Java.jdtls_bridge.get(args["id"]) + future = Java.jdtls_bridge.get(args['id']) if not future: - print("Unable to find a future for this id") + print('Unable to find a future for this id') return future.set_result(args) From fe97c7e4df4894fa4a6aba801871772750ba1d19 Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Sat, 17 Sep 2022 14:57:09 +0200 Subject: [PATCH 16/17] Java: Adapt to new installer interface --- modules/adapters/java.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index cb177e47..d6b09141 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -1,15 +1,3 @@ -# Basic idea here is... -# When installing the adapter it would add the com.microsoft.java.debug.plugin-0.30.0.jar as a plugin to lsp-jdts -# When starting the adapter it will ask lsp-jdts to start the adapter by calling the command lsp_jdts_start_debugging exposed by lsp-jdts -# lsp-jdts will then call debugger_lsp_jdts_start_debugging_response after it has started the adapter -# Debugger will then connect to the given port and start debugging - -# see https://github.com/Microsoft/java-debug for how the lsp side needs to be setup -# command to start the debug session -# { -# 'command': 'vscode.java.startDebugSession' -# } - from ..typecheck import Optional, Dict, Any, Tuple from ..import core from ..import dap @@ -28,6 +16,11 @@ class Java(dap.AdapterConfiguration): type = 'java' docs = 'https://github.com/redhat-developer/vscode-java/blob/master/README.md' + installer = util.OpenVsxInstaller( + type='java', + repo='vscjava/vscode-java-debug' + ) + async def start(self, log, configuration): # Make sure LSP and LSP-JDTLS are installed pc_settings = sublime.load_settings('Package Control.sublime-settings') @@ -60,25 +53,6 @@ async def start(self, log, configuration): return dap.SocketTransport(log, 'localhost', port) - async def install(self, log): - url = await util.openvsx.latest_release_vsix('vscjava', 'vscode-java-debug') - await util.vscode.install(self.type, url, log) - - async def installed_status(self, log): - return await util.openvsx.installed_status('vscjava', 'vscode-java-debug', self.installed_version) - - @property - def installed_version(self) -> Optional[str]: - return util.vscode.installed_version(self.type) - - @property - def configuration_snippets(self) -> Optional[list]: - return util.vscode.configuration_snippets(self.type) - - @property - def configuration_schema(self) -> Optional[dict]: - return util.vscode.configuration_schema(self.type) - async def on_navigate_to_source(self, source: dap.SourceLocation) -> Optional[Tuple[str, str]]: if not source.source.path or not source.source.path.startswith('jdt:'): return None From 43c28bafd4ced21f2bae0500241d87ce1cb52bda Mon Sep 17 00:00:00 2001 From: Lucas Alber Date: Sat, 17 Sep 2022 15:06:00 +0200 Subject: [PATCH 17/17] Java: Add jdtls connection timeout --- modules/adapters/java.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/adapters/java.py b/modules/adapters/java.py index d6b09141..6d040703 100644 --- a/modules/adapters/java.py +++ b/modules/adapters/java.py @@ -110,8 +110,6 @@ async def lsp_request(self, method, params) -> Any: ''' Returns the response or raises an exception. ''' - - # probably need to add some sort of timeout future = core.Future() id = Java.jdtls_bridge_current_id @@ -125,8 +123,11 @@ async def lsp_request(self, method, params) -> Any: 'debugger_jdtls_bridge_request', {'id': id, 'callback_command': 'debugger_jdtls_bridge_response', 'method': method, 'params': params} ) - - command_response = await future + sublime.set_timeout(lambda: future.cancel(), 2500) + try: + command_response = await future + except core.CancelledError: + raise core.Error('Unable to connect to LSP-jdtls (timed out)') del Java.jdtls_bridge[id]