From 4a2f72e183b3e59ecafe216728b68f048057cc0c Mon Sep 17 00:00:00 2001 From: Diego Velez Date: Sun, 5 Apr 2026 20:49:13 -0400 Subject: [PATCH 1/6] refactor!: download packages asynchronously --- lua/java.lua | 31 +++++---- lua/pkgm/downloaders/curl.lua | 37 +++++----- lua/pkgm/downloaders/powershell.lua | 35 ++++++---- lua/pkgm/downloaders/wget.lua | 36 +++++----- lua/pkgm/manager.lua | 100 ++++++++++++++++++---------- 5 files changed, 148 insertions(+), 91 deletions(-) diff --git a/lua/java.lua b/lua/java.lua index 59332f3..598d2cf 100644 --- a/lua/java.lua +++ b/lua/java.lua @@ -30,14 +30,15 @@ function M.setup(custom_config) ---------------------------------------------------------------------- local Manager = require('pkgm.manager') local pkgm = Manager() + local to_install = {} - pkgm:install('jdtls', config.jdtls.version) + table.insert(to_install, { name = 'jdtls', version = config.jdtls.version }) if config.java_test.enable then ---------------------------------------------------------------------- -- test -- ---------------------------------------------------------------------- - pkgm:install('java-test', config.java_test.version) + table.insert(to_install, { name = 'java-test', version = config.java_test.version }) M.test = { run_current_class = test_api.run_current_class, @@ -54,7 +55,7 @@ function M.setup(custom_config) ---------------------------------------------------------------------- -- debugger -- ---------------------------------------------------------------------- - pkgm:install('java-debug', config.java_debug_adapter.version) + table.insert(to_install, { name = 'java-debug', version = config.java_debug_adapter.version }) require('java-dap').setup() M.dap = { @@ -65,23 +66,29 @@ function M.setup(custom_config) end if config.spring_boot_tools.enable then - pkgm:install('spring-boot-tools', config.spring_boot_tools.version) + table.insert(to_install, { name = 'spring-boot-tools', version = config.spring_boot_tools.version }) end if config.lombok.enable then - pkgm:install('lombok', config.lombok.version) + table.insert(to_install, { name = 'lombok', version = config.lombok.version }) end if config.jdk.auto_install then - pkgm:install('openjdk', config.jdk.version) + table.insert(to_install, { name = 'openjdk', version = config.jdk.version }) end - ---------------------------------------------------------------------- - -- init -- - ---------------------------------------------------------------------- - require('java.startup.lsp_setup').setup(config) - require('java.startup.decompile-watcher').setup() - require('java-refactor').setup() + pkgm:install_all( + to_install, + vim.schedule_wrap(function() + ---------------------------------------------------------------------- + -- init -- + ---------------------------------------------------------------------- + require('java.startup.lsp_setup').setup(config) + require('java.startup.decompile-watcher').setup() + require('java-refactor').setup() + vim.lsp.enable('jdtls') + end) + ) end ---------------------------------------------------------------------- diff --git a/lua/pkgm/downloaders/curl.lua b/lua/pkgm/downloaders/curl.lua index 2009ab8..b44b813 100644 --- a/lua/pkgm/downloaders/curl.lua +++ b/lua/pkgm/downloaders/curl.lua @@ -35,29 +35,34 @@ function Curl:_init(opts) end ---Download file using curl ----@return string|nil # Path to downloaded file, or nil on failure ----@return string|nil # Error message if failed -function Curl:download() +---@param on_finished fun(file_path: string|nil, err: string|nil) +function Curl:download(on_finished) log.debug('curl downloading:', self.url, 'to', self.dest) - local cmd = string.format( - 'curl --retry %d --connect-timeout %d -o %s %s', + local cmd = { + 'curl', + '--retry', self.retry_count, + '--connect-timeout', self.timeout, - vim.fn.shellescape(self.dest), - vim.fn.shellescape(self.url) - ) + '-o', + self.dest, + self.url, + } log.debug('curl command:', cmd) - local result = vim.fn.system(cmd) - local exit_code = vim.v.shell_error + vim.system(cmd, { text = true }, function(out) + local result = out.stderr + local exit_code = out.code - if exit_code ~= 0 then - log.error('curl failed:', exit_code, result) - return nil, string.format('curl failed (exit %d): %s', exit_code, result) - end + if exit_code ~= 0 then + log.error('curl failed:', exit_code, result) + on_finished(nil, string.format('curl failed (exit %d): %s', exit_code, result)) + return + end - log.debug('curl download completed:', self.dest) - return self.dest, nil + log.debug('curl download completed:', self.dest) + on_finished(self.dest, nil) + end) end return Curl diff --git a/lua/pkgm/downloaders/powershell.lua b/lua/pkgm/downloaders/powershell.lua index f0a5434..c19fd51 100644 --- a/lua/pkgm/downloaders/powershell.lua +++ b/lua/pkgm/downloaders/powershell.lua @@ -36,8 +36,8 @@ function PowerShell:_init(opts) end ---Download file using PowerShell ----@return string # Path to downloaded file -function PowerShell:download() +---@param on_finished fun(file_path: string) +function PowerShell:download(on_finished) local pwsh = vim.fn.executable('pwsh') == 1 and 'pwsh' or 'powershell' log.debug('PowerShell downloading:', self.url, 'to', self.dest) log.debug('Using PowerShell binary:', pwsh) @@ -49,24 +49,33 @@ function PowerShell:download() self.dest ) - local cmd = string.format( + local inner_cmd = string.format( -- luacheck: ignore - "%s -NoProfile -NonInteractive -Command \"$ProgressPreference = 'SilentlyContinue'; $ErrorActionPreference = 'Stop'; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; %s\"", - pwsh, + "$ProgressPreference = 'SilentlyContinue'; $ErrorActionPreference = 'Stop'; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; %s", pwsh_cmd ) + + local cmd = { + pwsh, + '-NoProfile', + '-NonInteractive', + '-Command', + inner_cmd, + } log.debug('PowerShell command:', cmd) - local result = vim.fn.system(cmd) - local exit_code = vim.v.shell_error + vim.system(cmd, { text = true }, function(out) + local result = out.stderr + local exit_code = out.code - if exit_code ~= 0 then - local err = string.format('PowerShell download failed (exit %d): %s', exit_code, result) - err_util.throw(err) - end + if exit_code ~= 0 then + local err = string.format('PowerShell download failed (exit %d): %s', exit_code, result) + err_util.throw(err) + end - log.debug('PowerShell download completed:', self.dest) - return self.dest + log.debug('PowerShell download completed:', self.dest) + on_finished(self.dest) + end) end return PowerShell diff --git a/lua/pkgm/downloaders/wget.lua b/lua/pkgm/downloaders/wget.lua index fff6629..c00f594 100644 --- a/lua/pkgm/downloaders/wget.lua +++ b/lua/pkgm/downloaders/wget.lua @@ -35,29 +35,33 @@ function Wget:_init(opts) end ---Download file using wget ----@return string|nil # Path to downloaded file, or nil on failure ----@return string|nil # Error message if failed -function Wget:download() +---@param on_finished fun(file_path: string|nil, err: string|nil) +function Wget:download(on_finished) log.debug('wget downloading:', self.url, 'to', self.dest) - local cmd = string.format( - 'wget -t %d -T %d -O %s %s', + local cmd = { + 'wget', + '-t', self.retry_count, + '-T', self.timeout, - vim.fn.shellescape(self.dest), - vim.fn.shellescape(self.url) - ) + '-O', + self.dest, + self.url, + } log.debug('wget command:', cmd) - local result = vim.fn.system(cmd) - local exit_code = vim.v.shell_error + vim.system(cmd, { text = true }, function(out) + local result = out.stderr + local exit_code = out.code - if exit_code ~= 0 then - log.error('wget failed:', exit_code, result) - return nil, string.format('wget failed (exit %d): %s', exit_code, result) - end + if exit_code ~= 0 then + log.error('wget failed:', exit_code, result) + on_finished(nil, string.format('wget failed (exit %d): %s', exit_code, result)) + end - log.debug('wget download completed:', self.dest) - return self.dest, nil + log.debug('wget download completed:', self.dest) + on_finished(self.dest, nil) + end) end return Wget diff --git a/lua/pkgm/manager.lua b/lua/pkgm/manager.lua index f5b8257..dba5c6f 100644 --- a/lua/pkgm/manager.lua +++ b/lua/pkgm/manager.lua @@ -19,33 +19,63 @@ function Manager:_init(specs) log.debug('Manager initialized with ' .. #self.specs .. ' specs') end ----Download and extract a package +---@class Package +---@field name string +---@field version string + +---Download and extract multiples packages asynchronously +---@param packages Package[] +---@param callback fun() +function Manager:install_all(packages, callback) + local remaining = #packages + if remaining == 0 then + callback() + return + end + + local on_finished = function() + remaining = remaining - 1 + if remaining == 0 then + callback() + end + end + + for _, package in ipairs(packages) do + self:install(package.name, package.version, on_finished) + end +end + +---Download and extract a package asynchronously ---@param name string ---@param version string ----@return string # Installation directory path -function Manager:install(name, version) +---@param on_finished fun(install_dir: string) +function Manager:install(name, version, on_finished) log.debug('Installing package:', name, version) if self:is_installed(name, version) then local install_dir = self:get_install_dir(name, version) log.debug('Package already installed:', install_dir) - return install_dir + on_finished(install_dir) + return end notify.info('Installing package ' .. name .. ' version ' .. version) local spec = self:find_spec(name, version) local url = spec:get_url(name, version) - local downloaded_file = self:download_package(url) - local install_dir = self:get_install_dir(name, version) - log.debug('Install directory:', install_dir) + self:download_package(url, function(downloaded_file) + local install_dir = self:get_install_dir(name, version) + + log.debug('Install directory:', install_dir) - self:extract_package(downloaded_file, install_dir) + self:extract_package(downloaded_file, install_dir) - log.debug('Package installed successfully:', install_dir) + log.debug('Package installed successfully:', install_dir) + notify.info('Package installed successfully ' .. name) - return install_dir + on_finished(install_dir) + end) end ---Check if package is installed @@ -133,23 +163,23 @@ end ---Download package from URL ---@private ---@param url string ----@return string # Downloaded file path -function Manager:download_package(url) +---@param on_finished fun(string) +function Manager:download_package(url, on_finished) log.debug('Using URL:', url) local downloader = downloader_factory.get_downloader({ url = url }) log.debug('Starting download...') - local downloaded_file, err = downloader:download() - - if not downloaded_file then - err_util.throw(err or 'Download failed') - end + downloader:download(function(downloaded_file, err) + if not downloaded_file then + err_util.throw(err or 'Download failed') + end - log.debug('Downloaded to:', downloaded_file) + log.debug('Downloaded to:', downloaded_file) - return downloaded_file + on_finished(downloaded_file) + end) end ---Extract package to installation directory @@ -157,27 +187,29 @@ end ---@param downloaded_file string ---@param install_dir string function Manager:extract_package(downloaded_file, install_dir) - local extractor = extractor_factory.get_extractor({ - source = downloaded_file, - dest = install_dir, - }) + vim.schedule(function() + local extractor = extractor_factory.get_extractor({ + source = downloaded_file, + dest = install_dir, + }) - vim.fn.mkdir(install_dir, 'p') + vim.fn.mkdir(install_dir, 'p') - log.debug('Starting extraction...') + log.debug('Starting extraction...') - local success, err = extractor:extract() + local success, err = extractor:extract() - if not success then - vim.fn.delete(install_dir, 'rf') - vim.fn.delete(downloaded_file) - err_util.throw(err or 'Extraction failed') - end + if not success then + vim.fn.delete(install_dir, 'rf') + vim.fn.delete(downloaded_file) + err_util.throw(err or 'Extraction failed') + end - log.debug('Extraction completed') + log.debug('Extraction completed') - vim.fn.delete(downloaded_file) - log.debug('Cleaned up temporary file') + vim.fn.delete(downloaded_file) + log.debug('Cleaned up temporary file') + end) end return Manager From 1828fd58127273d5ab94989506a6c5da319a546e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20V=C3=A9lez?= Date: Mon, 8 Jun 2026 10:24:53 -0400 Subject: [PATCH 2/6] fix: missing return in failure branch --- lua/pkgm/downloaders/wget.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/pkgm/downloaders/wget.lua b/lua/pkgm/downloaders/wget.lua index c00f594..5d0fb1a 100644 --- a/lua/pkgm/downloaders/wget.lua +++ b/lua/pkgm/downloaders/wget.lua @@ -57,6 +57,7 @@ function Wget:download(on_finished) if exit_code ~= 0 then log.error('wget failed:', exit_code, result) on_finished(nil, string.format('wget failed (exit %d): %s', exit_code, result)) + return end log.debug('wget download completed:', self.dest) From 14daca77b40d9e512ce235308260a87bf65f4254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20V=C3=A9lez?= Date: Mon, 8 Jun 2026 10:49:14 -0400 Subject: [PATCH 3/6] chore: add Nix flake dev shell files to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f8b34a1..2a9bbf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ vendor/plenary.nvim .test_plugins proj +.envrc +.direnv +flake.* From ada870e126bc94f51fadc3b876f4aef0282989cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20V=C3=A9lez?= Date: Mon, 8 Jun 2026 10:53:52 -0400 Subject: [PATCH 4/6] docs: update readme and vim documentation panvimdoc was used to auto-generate the vim doc file --- README.md | 6 +++--- doc/nvim-java.txt | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d7776b2..a6e7918 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Just install and start writing `public static void main(String[] args)`. **Requirements:** Neovim 0.11.5+ +**Do not call `vim.lsp.enable('jdtls')` yourself!** + ### Using `vim.pack` ```lua @@ -60,7 +62,6 @@ vim.pack.add({ }) require('java').setup() -vim.lsp.enable('jdtls') ``` ### Using `lazy.nvim` @@ -72,7 +73,6 @@ Install using [lazy.nvim](https://github.com/folke/lazy.nvim): 'nvim-java/nvim-java', config = function() require('java').setup() - vim.lsp.enable('jdtls') end, } ``` @@ -291,7 +291,7 @@ require('java').settings.change_runtime() ## :clamp: How to Use JDK X.X Version?
- + :small_orange_diamond:details Use `vim.lsp.config()` to override the default JDTLS settings: diff --git a/doc/nvim-java.txt b/doc/nvim-java.txt index 709c13b..ce12ff4 100644 --- a/doc/nvim-java.txt +++ b/doc/nvim-java.txt @@ -1,4 +1,4 @@ -*nvim-java.txt* For Neovim >= 0.11.5 Last change: 2026 February 05 +*nvim-java.txt* For Neovim >= 0.11.5 Last change: 2026 June 08 ============================================================================== Table of Contents *nvim-java-table-of-contents* @@ -56,6 +56,8 @@ HOW TO INSTALL *nvim-java-how-to-install* **Requirements:** Neovim 0.11.5+ +**Do not call vim.lsp.enable('jdtls') yourself!** + USING VIM.PACK ~ @@ -72,25 +74,23 @@ USING VIM.PACK ~ }) require('java').setup() - vim.lsp.enable('jdtls') < USING LAZY.NVIM ~ -Install using lazy.nvim : +Install using lazy.nvim >lua { 'nvim-java/nvim-java', config = function() require('java').setup() - vim.lsp.enable('jdtls') end, } < -Yep! That’s all :) +Yep!That’s all :) COMMANDS *nvim-java-commands* From def3865f2e673ba7e668cb22e0280b4e1eb5353f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20V=C3=A9lez?= Date: Mon, 8 Jun 2026 11:11:06 -0400 Subject: [PATCH 5/6] fix: wrap throw with vim.schedule --- lua/pkgm/manager.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/pkgm/manager.lua b/lua/pkgm/manager.lua index dba5c6f..bb652a8 100644 --- a/lua/pkgm/manager.lua +++ b/lua/pkgm/manager.lua @@ -173,7 +173,9 @@ function Manager:download_package(url, on_finished) downloader:download(function(downloaded_file, err) if not downloaded_file then - err_util.throw(err or 'Download failed') + vim.schedule(function() + err_util.throw(err or 'Download failed') + end) end log.debug('Downloaded to:', downloaded_file) From 735b5a914fb48a3dbfee245f4b8c693398e5a491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20V=C3=A9lez?= Date: Mon, 8 Jun 2026 11:46:30 -0400 Subject: [PATCH 6/6] refactor: propagate errors to install_all --- lua/pkgm/downloaders/curl.lua | 5 ++- lua/pkgm/downloaders/powershell.lua | 7 ++-- lua/pkgm/downloaders/wget.lua | 5 ++- lua/pkgm/manager.lua | 61 ++++++++++++++++++----------- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/lua/pkgm/downloaders/curl.lua b/lua/pkgm/downloaders/curl.lua index b44b813..d9c6526 100644 --- a/lua/pkgm/downloaders/curl.lua +++ b/lua/pkgm/downloaders/curl.lua @@ -55,8 +55,9 @@ function Curl:download(on_finished) local exit_code = out.code if exit_code ~= 0 then - log.error('curl failed:', exit_code, result) - on_finished(nil, string.format('curl failed (exit %d): %s', exit_code, result)) + local err = string.format('curl failed (exit %d): %s', exit_code, result) + log.error(err) + on_finished(nil, err) return end diff --git a/lua/pkgm/downloaders/powershell.lua b/lua/pkgm/downloaders/powershell.lua index c19fd51..cf44716 100644 --- a/lua/pkgm/downloaders/powershell.lua +++ b/lua/pkgm/downloaders/powershell.lua @@ -1,6 +1,5 @@ local class = require('java-core.utils.class') local log = require('java-core.utils.log2') -local err_util = require('java-core.utils.errors') local path = require('java-core.utils.path') ---@class java-core.PowerShell @@ -36,7 +35,7 @@ function PowerShell:_init(opts) end ---Download file using PowerShell ----@param on_finished fun(file_path: string) +---@param on_finished fun(file_path: string|nil, err: string|nil) function PowerShell:download(on_finished) local pwsh = vim.fn.executable('pwsh') == 1 and 'pwsh' or 'powershell' log.debug('PowerShell downloading:', self.url, 'to', self.dest) @@ -70,7 +69,9 @@ function PowerShell:download(on_finished) if exit_code ~= 0 then local err = string.format('PowerShell download failed (exit %d): %s', exit_code, result) - err_util.throw(err) + log.error(err) + on_finished(nil, err) + return end log.debug('PowerShell download completed:', self.dest) diff --git a/lua/pkgm/downloaders/wget.lua b/lua/pkgm/downloaders/wget.lua index 5d0fb1a..691627d 100644 --- a/lua/pkgm/downloaders/wget.lua +++ b/lua/pkgm/downloaders/wget.lua @@ -55,8 +55,9 @@ function Wget:download(on_finished) local exit_code = out.code if exit_code ~= 0 then - log.error('wget failed:', exit_code, result) - on_finished(nil, string.format('wget failed (exit %d): %s', exit_code, result)) + local err = string.format('wget failed (exit %d): %s', exit_code, result) + log.error(err) + on_finished(nil, err) return end diff --git a/lua/pkgm/manager.lua b/lua/pkgm/manager.lua index bb652a8..8826e2c 100644 --- a/lua/pkgm/manager.lua +++ b/lua/pkgm/manager.lua @@ -33,8 +33,11 @@ function Manager:install_all(packages, callback) return end - local on_finished = function() + local on_finished = function(name, err) remaining = remaining - 1 + if err then + notify.error('Failed to install package: ' .. name) + end if remaining == 0 then callback() end @@ -48,33 +51,45 @@ end ---Download and extract a package asynchronously ---@param name string ---@param version string ----@param on_finished fun(install_dir: string) +---@param on_finished fun(name: string, err: string|nil) function Manager:install(name, version, on_finished) log.debug('Installing package:', name, version) if self:is_installed(name, version) then local install_dir = self:get_install_dir(name, version) log.debug('Package already installed:', install_dir) - on_finished(install_dir) + on_finished(name, nil) return end notify.info('Installing package ' .. name .. ' version ' .. version) - local spec = self:find_spec(name, version) - local url = spec:get_url(name, version) + local ok, url = pcall(function() + local spec = self:find_spec(name, version) + return spec:get_url(name, version) + end) + if not ok then + on_finished(name, url) + return + end - self:download_package(url, function(downloaded_file) - local install_dir = self:get_install_dir(name, version) + self:download_package(url, function(downloaded_file, download_err) + if download_err then + on_finished(name, download_err) + return + end + local install_dir = self:get_install_dir(name, version) log.debug('Install directory:', install_dir) - - self:extract_package(downloaded_file, install_dir) - - log.debug('Package installed successfully:', install_dir) - notify.info('Package installed successfully ' .. name) - - on_finished(install_dir) + self:extract_package(downloaded_file, install_dir, function(extract_err) + if extract_err then + on_finished(name, extract_err) + return + end + + notify.info('Package installed successfully ' .. name) + on_finished(name, nil) + end) end) end @@ -163,7 +178,7 @@ end ---Download package from URL ---@private ---@param url string ----@param on_finished fun(string) +---@param on_finished fun(string, string|nil) function Manager:download_package(url, on_finished) log.debug('Using URL:', url) @@ -172,15 +187,14 @@ function Manager:download_package(url, on_finished) log.debug('Starting download...') downloader:download(function(downloaded_file, err) - if not downloaded_file then - vim.schedule(function() - err_util.throw(err or 'Download failed') - end) + if err then + on_finished(downloaded_file, err) + return end log.debug('Downloaded to:', downloaded_file) - on_finished(downloaded_file) + on_finished(downloaded_file, nil) end) end @@ -188,7 +202,8 @@ end ---@private ---@param downloaded_file string ---@param install_dir string -function Manager:extract_package(downloaded_file, install_dir) +---@param on_finished fun(string|nil) +function Manager:extract_package(downloaded_file, install_dir, on_finished) vim.schedule(function() local extractor = extractor_factory.get_extractor({ source = downloaded_file, @@ -204,13 +219,15 @@ function Manager:extract_package(downloaded_file, install_dir) if not success then vim.fn.delete(install_dir, 'rf') vim.fn.delete(downloaded_file) - err_util.throw(err or 'Extraction failed') + on_finished(err) + return end log.debug('Extraction completed') vim.fn.delete(downloaded_file) log.debug('Cleaned up temporary file') + on_finished(nil) end) end