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.* 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* 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..d9c6526 100644 --- a/lua/pkgm/downloaders/curl.lua +++ b/lua/pkgm/downloaders/curl.lua @@ -35,29 +35,35 @@ 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 + local err = string.format('curl failed (exit %d): %s', exit_code, result) + log.error(err) + on_finished(nil, err) + 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..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,8 +35,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|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) log.debug('Using PowerShell binary:', pwsh) @@ -49,24 +48,35 @@ 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) + log.error(err) + on_finished(nil, err) + return + 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..691627d 100644 --- a/lua/pkgm/downloaders/wget.lua +++ b/lua/pkgm/downloaders/wget.lua @@ -35,29 +35,35 @@ 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 + local err = string.format('wget failed (exit %d): %s', exit_code, result) + log.error(err) + on_finished(nil, err) + return + 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..8826e2c 100644 --- a/lua/pkgm/manager.lua +++ b/lua/pkgm/manager.lua @@ -19,33 +19,78 @@ 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(name, err) + remaining = remaining - 1 + if err then + notify.error('Failed to install package: ' .. name) + end + 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(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) - return 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 downloaded_file = self:download_package(url) - local install_dir = self:get_install_dir(name, version) - - log.debug('Install directory:', install_dir) - - self:extract_package(downloaded_file, install_dir) + 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 - log.debug('Package installed successfully:', install_dir) + self:download_package(url, function(downloaded_file, download_err) + if download_err then + on_finished(name, download_err) + return + end - return install_dir + local install_dir = self:get_install_dir(name, version) + log.debug('Install directory:', 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 ---Check if package is installed @@ -133,51 +178,57 @@ end ---Download package from URL ---@private ---@param url string ----@return string # Downloaded file path -function Manager:download_package(url) +---@param on_finished fun(string, string|nil) +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 err then + on_finished(downloaded_file, err) + return + end - log.debug('Downloaded to:', downloaded_file) + log.debug('Downloaded to:', downloaded_file) - return downloaded_file + on_finished(downloaded_file, nil) + end) end ---Extract package to installation directory ---@private ---@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, - }) +---@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, + 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) + on_finished(err) + return + 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') + on_finished(nil) + end) end return Manager