Skip to content
This repository was archived by the owner on Nov 30, 2025. It is now read-only.

Commit 4274221

Browse files
authored
feat: dap config and adapter for java (#3)
1 parent 13b523e commit 4274221

File tree

8 files changed

+372
-13
lines changed

8 files changed

+372
-13
lines changed

lua/java.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
local server = require('java.server')
22
local config = require('java.config')
3-
local settings = require('java.settings')
3+
local lsp = require('java.utils.lsp')
44

55
---@class Java
66
---@field config JavaConfig
@@ -22,7 +22,7 @@ function M.get_config(root_markers)
2222
end
2323

2424
M.__run = function()
25-
settings.change_settings({})
25+
print('>>>>>')
2626
end
2727

2828
return M

lua/java/dap.lua

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
local log = require('java.utils.log')
2+
local lsp = require('java.utils.lsp')
3+
local JavaDebug = require('java.ls.clients.java-debug-client')
4+
local Promise = require('java.utils.promise')
5+
6+
local M = {}
7+
8+
function M.setup_dap_on_attach()
9+
vim.api.nvim_create_autocmd('LspAttach', {
10+
pattern = '*',
11+
callback = function(a)
12+
local client = vim.lsp.get_client_by_id(a.data.client_id)
13+
14+
if client.name == 'jdtls' then
15+
Promise.all({
16+
M.set_dap_config(),
17+
M.set_dap_adapter(),
18+
}):catch(function(err)
19+
local msg = [[Faild to set DAP configuration & adapter]]
20+
21+
error(msg, err)
22+
log.error(msg, err)
23+
end)
24+
end
25+
end,
26+
})
27+
end
28+
29+
function M.set_dap_config()
30+
log.info('setting dap configurations for java')
31+
32+
local dap = require('dap')
33+
34+
return M.get_dap_config():thenCall(
35+
---@type JavaDapConfigurationList
36+
function(res)
37+
dap.configurations.java = res
38+
end
39+
)
40+
end
41+
42+
function M.set_dap_adapter()
43+
log.info('setting dap adapter for java')
44+
45+
local dap = require('dap')
46+
47+
return M.get_dap_adapter():thenCall(
48+
---@type JavaDapAdapter
49+
function(res)
50+
log.debug('adapter settings: ', res)
51+
dap.adapters.java = res
52+
end
53+
)
54+
end
55+
56+
---@class JavaDapAdapter
57+
---@field type string
58+
---@field host string
59+
---@field port integer
60+
61+
---Returns the dap adapter config
62+
---@return Promise
63+
function M.get_dap_adapter()
64+
log.info('creating dap adapter for java')
65+
66+
local client = lsp.get_jdtls_client()
67+
68+
--@TODO when thrown from the function instead of the promise,
69+
--error handling has to be done in two places
70+
if not client then
71+
local msg = 'no active jdtls client was found'
72+
73+
log.error(msg)
74+
error(msg)
75+
end
76+
77+
local jdtlsClient = JavaDebug:new({
78+
client = client,
79+
})
80+
81+
return jdtlsClient:start_debug_session():thenCall(
82+
---@param port JavaDebugStartDebugSessionResponse
83+
function(port)
84+
return {
85+
type = 'server',
86+
host = '127.0.0.1',
87+
port = port,
88+
}
89+
end
90+
)
91+
end
92+
93+
---@class JavaDapConfiguration
94+
---@field name string
95+
---@field projectName string
96+
---@field mainClass string
97+
---@field javaExec string
98+
---@field modulePaths string[]
99+
---@field classPaths string[]
100+
---@field request string
101+
102+
---@alias JavaDapConfigurationList JavaDapConfiguration[]
103+
104+
---Returns the dap configuration for the current project
105+
---@return Promise
106+
function M.get_dap_config()
107+
log.info('creating dap configuration for java')
108+
109+
local client = lsp.get_jdtls_client()
110+
111+
if not client then
112+
local msg = 'no active jdtls client was found'
113+
114+
log.error(msg)
115+
error(msg)
116+
end
117+
118+
local jdtlsClient = JavaDebug:new({
119+
client = client,
120+
})
121+
122+
---@type JavaDebugResolveMainClassResponse
123+
local main_classes_info_list
124+
125+
return jdtlsClient
126+
:resolve_main_class()
127+
:thenCall(
128+
---@param main_classes_info JavaDebugResolveMainClassResponse
129+
function(main_classes_info)
130+
main_classes_info_list = main_classes_info
131+
132+
---@type Promise[]
133+
local classpath_promises = {}
134+
---@type Promise[]
135+
local java_exec_promises = {}
136+
137+
for _, single_class_info in ipairs(main_classes_info) do
138+
table.insert(
139+
classpath_promises,
140+
jdtlsClient:resolve_classpath(
141+
single_class_info.mainClass,
142+
single_class_info.projectName
143+
)
144+
)
145+
146+
table.insert(
147+
java_exec_promises,
148+
jdtlsClient:resolve_java_executable(
149+
single_class_info.mainClass,
150+
single_class_info.projectName
151+
)
152+
)
153+
end
154+
155+
return Promise.all({
156+
Promise.all(classpath_promises),
157+
Promise.all(java_exec_promises),
158+
})
159+
end
160+
)
161+
:thenCall(function(result)
162+
return M.__get_dap_java_config(
163+
main_classes_info_list,
164+
result[1],
165+
result[2]
166+
)
167+
end)
168+
end
169+
170+
function M.__get_dap_java_config(main_classes, classpaths, java_execs)
171+
local len = #main_classes
172+
local dap_config_list = {}
173+
174+
for i = 1, len do
175+
local main_class = main_classes[i].mainClass
176+
local project_name = main_classes[i].projectName
177+
local module_paths = classpaths[i][1]
178+
local class_paths = classpaths[i][2]
179+
180+
table.insert(dap_config_list, {
181+
name = string.format('%s -> %s', project_name, main_class),
182+
projectName = project_name,
183+
mainClass = main_class,
184+
javaExec = java_execs[i],
185+
request = 'launch',
186+
type = 'java',
187+
modulePaths = module_paths,
188+
classPaths = class_paths,
189+
})
190+
end
191+
192+
return dap_config_list
193+
end
194+
195+
return M
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
local JDTLSClient = require('java.ls.clients.jdtls-client')
2+
3+
local M = JDTLSClient:new()
4+
5+
---@class JavaDebugResolveMainClassRecord
6+
---@field mainClass string
7+
---@field projectName string
8+
---@field fileName string
9+
10+
---@alias JavaDebugResolveMainClassResponse JavaDebugResolveMainClassRecord[]
11+
12+
---Returns a list of main classes in the current workspace
13+
---@return Promise
14+
function M:resolve_main_class()
15+
return self:execute_command('vscode.java.resolveMainClass')
16+
end
17+
18+
---@alias JavaDebugResolveClasspathResponse string[][]
19+
20+
---Returns module paths and class paths of a given main class
21+
---@param project_name string
22+
---@param main_class string
23+
---@return Promise
24+
function M:resolve_classpath(main_class, project_name)
25+
return self:execute_command(
26+
'vscode.java.resolveClasspath',
27+
{ main_class, project_name }
28+
)
29+
end
30+
31+
---@alias JavaDebugResolveJavaExecutableResponse string
32+
33+
---Returns the path to java executable for a given main class
34+
---@param project_name string
35+
---@param main_class string
36+
---@return Promise
37+
function M:resolve_java_executable(main_class, project_name)
38+
return self:execute_command('vscode.java.resolveJavaExecutable', {
39+
main_class,
40+
project_name,
41+
})
42+
end
43+
44+
---@alias JavaDebugCheckProjectSettingsResponse boolean
45+
46+
---Returns true if the project settings is the expected
47+
---@param project_name string
48+
---@param main_class string
49+
---@param inheritedOptions boolean
50+
---@param expectedOptions { [string]: any }
51+
---@return Promise
52+
function M:check_project_settings(
53+
main_class,
54+
project_name,
55+
inheritedOptions,
56+
expectedOptions
57+
)
58+
return self:execute_command(
59+
'vscode.java.checkProjectSettings',
60+
vim.fn.json_encode({
61+
className = main_class,
62+
projectName = project_name,
63+
inheritedOptions = inheritedOptions,
64+
expectedOptions = expectedOptions,
65+
})
66+
)
67+
end
68+
69+
---@alias JavaDebugStartDebugSessionResponse integer
70+
71+
---Starts a debug session and returns the port number
72+
---@return Promise
73+
function M:start_debug_session()
74+
return self:execute_command('vscode.java.startDebugSession')
75+
end
76+
77+
function M:build_workspace()
78+
return self:execute_command('vscode.java.buildWorkspace')
79+
end
80+
81+
return M
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
local log = require('java.utils.log')
2+
local Promise = require('java.utils.promise')
3+
4+
---@class JDTLSClient
5+
---@field client LSPClient
6+
local M = {}
7+
8+
function M:new(o)
9+
o = o or {}
10+
setmetatable(o, self)
11+
self.__index = self
12+
return o
13+
end
14+
15+
---Executes a workspace/executeCommand and returns the result
16+
---@param command string
17+
---@param arguments? string | string[]
18+
---@param buffer? integer
19+
---@return Promise
20+
function M:execute_command(command, arguments, buffer)
21+
return Promise.new(function(resolve, reject)
22+
local cmd_info = {
23+
command = command,
24+
arguments = arguments,
25+
}
26+
27+
log.debug('workspace/executeCommand: ', cmd_info)
28+
29+
self.client.request('workspace/executeCommand', cmd_info, function(err, res)
30+
if err then
31+
reject(err)
32+
else
33+
resolve(res)
34+
end
35+
end, buffer)
36+
end)
37+
end
38+
39+
function M:get_capability(...)
40+
local capability = self.client.server_capabilities
41+
42+
for _, value in ipairs({ ... }) do
43+
if type(capability) ~= 'table' then
44+
log.fmt_warn('Looking for capability: %s in value %s', value, capability)
45+
return nil
46+
end
47+
48+
capability = capability[value]
49+
end
50+
51+
return capability
52+
end
53+
54+
function M:has_command(command_name)
55+
local commands = self:get_capability('executeCommandProvider', 'commands')
56+
57+
if not commands then
58+
return false
59+
end
60+
61+
return vim.tbl_contains(commands, command_name)
62+
end
63+
64+
return M

lua/java/lspconfig-types.lua

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
---@field on_attach fun(client: object, bufnr: number) Callback invoked by Nvim's built-in client when attaching a buffer to a language server. Often used to set Nvim (buffer or global) options or to override the Nvim client properties (`server_capabilities`) after a language server attaches. Most commonly used for settings buffer local keybindings. See |lspconfig-keybindings| for a usage example.
1313
---@field settings table <string, string|table|boolean> The `settings` table is sent in `on_init` via a `workspace/didChangeConfiguration` notification from the Nvim client to the language server. These settings allow a user to change optional runtime settings of the language server.
1414

15+
---@class LSPClientRequestParameters
16+
---@field command string
17+
---@field arguments string | string[] | nil
18+
19+
---@class LSPClientResponse
20+
---@field err LSPClientResponseError
21+
22+
---@class LSPClientResponseError
23+
---@field code number
24+
---@field message string
25+
1526
---@class LSPClient
1627
---@field attached_buffers table<number, boolean>
1728
---@field cancel_request function
@@ -25,7 +36,7 @@
2536
---@field name string
2637
---@field notify fun(method: string, params: object): boolean
2738
---@field offset_encoding string
28-
---@field request function
39+
---@field request fun(method: string, params: LSPClientRequestParameters, callback: fun(err: any, result: any), bufnr?: number): any
2940
---@field request_sync function
3041
---@field requests object
3142
---@field rpc object

lua/java/settings.lua

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ function M.change_settings(settings)
1313

1414
local client = lsp.get_jdtls_client()
1515

16-
vim.print(client)
17-
1816
if not client then
1917
local msg = 'jdtls client not found'
2018
log.error(msg)

0 commit comments

Comments
 (0)