From 69e8516b0491791809a7a187df184cc81977a3d4 Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 16:11:38 +0100 Subject: [PATCH 1/8] feat: download java if not found - Enable the user to opt-in to auto download a version of OpenJDK if a working version is not found --- src/config.rs | 11 ++++++ src/java.rs | 2 + src/jdk.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/jdtls.rs | 3 +- src/util.rs | 44 ++++++++++++++++------ 5 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 src/jdk.rs diff --git a/src/config.rs b/src/config.rs index 4f677b0..c5b824f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,6 +29,17 @@ pub fn get_java_home(configuration: &Option, worktree: &Worktree) -> Opti } } +pub fn is_java_autodownload(configuration: &Option) -> bool { + configuration + .as_ref() + .and_then(|configuration| { + configuration + .pointer("jdk_auto_download") + .and_then(|enabled| enabled.as_bool()) + }) + .unwrap_or(false) +} + pub fn is_lombok_enabled(configuration: &Option) -> bool { configuration .as_ref() diff --git a/src/java.rs b/src/java.rs index 7bc8b11..86745aa 100644 --- a/src/java.rs +++ b/src/java.rs @@ -1,5 +1,6 @@ mod config; mod debugger; +mod jdk; mod jdtls; mod lsp; mod util; @@ -289,6 +290,7 @@ impl Extension for Java { &configuration, worktree, lombok_jvm_arg.into_iter().collect(), + language_server_id, )?); } diff --git a/src/jdk.rs b/src/jdk.rs new file mode 100644 index 0000000..b15cd43 --- /dev/null +++ b/src/jdk.rs @@ -0,0 +1,101 @@ +use std::path::PathBuf; + +use zed_extension_api::{ + self as zed, Architecture, DownloadedFileType, LanguageServerId, + LanguageServerInstallationStatus, Os, current_platform, download_file, + set_language_server_installation_status, +}; + +use crate::{ + jdk, + util::{get_curr_dir, get_java_exec_name, path_to_string, remove_all_files_except}, +}; + +const CORRETTO_REPO: &str = "corretto/corretto-25"; +const CORRETTO_UNIX_URL_TEMPLATE: &str = "https://corretto.aws/downloads/resources/{version}/amazon-corretto-{version}-{platform}-{arch}.tar.gz"; +const CORRETTO_WINDOWS_URL_TEMPLATE: &str = "https://corretto.aws/downloads/resources/{version}/amazon-corretto-{version}-{platform}-{arch}-jdk.zip"; + +fn build_corretto_url(version: &String, platform: &String, arch: &String) -> String { + match zed::current_platform().0 { + Os::Windows => CORRETTO_WINDOWS_URL_TEMPLATE + .replace("{version}", version) + .replace("{platform}", platform) + .replace("{arch}", arch), + _ => CORRETTO_UNIX_URL_TEMPLATE + .replace("{version}", version) + .replace("{platform}", platform) + .replace("{arch}", arch), + } +} + +// For now keep in this file as they are not used anywhere else +// otherwise move to util +pub fn get_architecture() -> zed::Result { + match zed::current_platform() { + (_, Architecture::Aarch64) => Ok("aarch64".to_string()), + (_, Architecture::X86) => Ok("x86".to_string()), + (_, Architecture::X8664) => Ok("x64".to_string()), + } +} + +pub fn get_platform() -> zed::Result { + match zed::current_platform() { + (Os::Mac, _) => Ok("macosx".to_string()), + (Os::Linux, _) => Ok("linux".to_string()), + (Os::Windows, _) => Ok("windows".to_string()), + } +} + +pub fn try_to_fetch_and_install_latest_jdk( + language_server_id: &LanguageServerId, +) -> zed::Result { + let version = zed::latest_github_release( + CORRETTO_REPO, + zed_extension_api::GithubReleaseOptions { + require_assets: false, + pre_release: false, + }, + )? + .version; + + let jdk_path = get_curr_dir()?.join("jdk"); + let install_path = jdk_path.join(&version); + + // Check for updates, if same version is already downloaded skip download + + set_language_server_installation_status( + language_server_id, + &LanguageServerInstallationStatus::CheckingForUpdate, + ); + + // windows and linux have bin as soon as extracted + // macos instead is under Contents/Home/bin + if !install_path.exists() { + set_language_server_installation_status( + language_server_id, + &LanguageServerInstallationStatus::Downloading, + ); + + let platform = get_platform()?; + let arch = get_architecture()?; + + download_file( + build_corretto_url(&version, &platform, &arch).as_str(), + path_to_string(install_path.clone())?.as_str(), + match zed::current_platform().0 { + Os::Windows => DownloadedFileType::Zip, + _ => DownloadedFileType::GzipTar, + }, + )?; + + // Remove older versions + let _ = remove_all_files_except(jdk_path, version.as_str()); + } + + let exec_path = match current_platform().0 { + Os::Mac => install_path.join("Contents/Home/bin"), + _ => install_path.join("bin"), + }; + + return Ok(exec_path.join(get_java_exec_name())); +} diff --git a/src/jdtls.rs b/src/jdtls.rs index ceccfa5..6f2de39 100644 --- a/src/jdtls.rs +++ b/src/jdtls.rs @@ -32,12 +32,13 @@ pub fn build_jdtls_launch_args( configuration: &Option, worktree: &Worktree, jvm_args: Vec, + language_server_id: &LanguageServerId, ) -> zed::Result> { if let Some(jdtls_launcher) = get_jdtls_launcher_from_path(worktree) { return Ok(vec![jdtls_launcher]); } - let java_executable = get_java_executable(configuration, worktree)?; + let java_executable = get_java_executable(configuration, worktree, language_server_id)?; let java_major_version = get_java_major_version(&java_executable)?; if java_major_version < 21 { return Err(JAVA_VERSION_ERROR.to_string()); diff --git a/src/util.rs b/src/util.rs index 2c44c2e..fb9ecbc 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,9 +4,14 @@ use std::{ fs, path::{Path, PathBuf}, }; -use zed_extension_api::{self as zed, Command, Os, Worktree, current_platform, serde_json::Value}; +use zed_extension_api::{ + self as zed, Command, LanguageServerId, Os, Worktree, current_platform, serde_json::Value, +}; -use crate::config::get_java_home; +use crate::{ + config::{get_java_home, is_java_autodownload}, + jdk::try_to_fetch_and_install_latest_jdk, +}; // Errors const EXPAND_ERROR: &str = "Failed to expand ~"; @@ -59,9 +64,10 @@ pub fn get_curr_dir() -> zed::Result { } /// Retrieve the path to a java exec either: -/// - defined by the user in `settings.json` +/// - defined by the user in `settings.json` under option `java_home` /// - from PATH /// - from JAVA_HOME +/// - from the bundled OpenJDK if option `jdk_auto_download` is true /// /// # Arguments /// @@ -79,11 +85,9 @@ pub fn get_curr_dir() -> zed::Result { pub fn get_java_executable( configuration: &Option, worktree: &Worktree, + language_server_id: &LanguageServerId, ) -> zed::Result { - let java_executable_filename = match current_platform().0 { - Os::Windows => "java.exe", - _ => "java", - }; + let java_executable_filename = get_java_exec_name(); // Get executable from $JAVA_HOME if let Some(java_home) = get_java_home(configuration, worktree) { @@ -93,10 +97,28 @@ pub fn get_java_executable( return Ok(java_executable); } // If we can't, try to get it from $PATH - worktree - .which(java_executable_filename) - .map(PathBuf::from) - .ok_or_else(|| JAVA_EXEC_NOT_FOUND_ERROR.to_string()) + if let Some(java_home) = worktree.which(java_executable_filename.as_str()) { + return Ok(PathBuf::from(java_home)); + } + + // If the user has set the option, retrieve the latest version of Corretto (OpenJDK) + if is_java_autodownload(configuration) { + try_to_fetch_and_install_latest_jdk(&language_server_id)?; + } + + Err(JAVA_EXEC_NOT_FOUND_ERROR.to_string()) +} + +/// Retrieve the executable for Java on this platform +/// +/// # Returns +/// +/// Returns the executable java name +pub fn get_java_exec_name() -> String { + match current_platform().0 { + Os::Windows => "java.exe".to_string(), + _ => "java".to_string(), + } } /// Retrieve the java major version accessible by the extension From ff9274a6f64c86da442ac65fed9b977932c1b888 Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 18:21:40 +0100 Subject: [PATCH 2/8] Retrieve extracted directory name --- src/config.rs | 2 +- src/jdk.rs | 65 +++++++++++++++++++++++++++++++++++---------------- src/util.rs | 28 +++++++++++----------- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/config.rs b/src/config.rs index c5b824f..3dc9fe3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,7 +34,7 @@ pub fn is_java_autodownload(configuration: &Option) -> bool { .as_ref() .and_then(|configuration| { configuration - .pointer("jdk_auto_download") + .pointer("/jdk_auto_download") .and_then(|enabled| enabled.as_bool()) }) .unwrap_or(false) diff --git a/src/jdk.rs b/src/jdk.rs index b15cd43..7a3cea1 100644 --- a/src/jdk.rs +++ b/src/jdk.rs @@ -6,31 +6,31 @@ use zed_extension_api::{ set_language_server_installation_status, }; -use crate::{ - jdk, - util::{get_curr_dir, get_java_exec_name, path_to_string, remove_all_files_except}, -}; +use crate::util::{get_curr_dir, path_to_string, remove_all_files_except}; + +// Errors +const JDK_DIR_ERROR: &str = "Failed to read into JDK install directory"; +const NO_JDK_DIR_ERROR: &str = "No match for jdk or corretto in the extracted directory"; const CORRETTO_REPO: &str = "corretto/corretto-25"; const CORRETTO_UNIX_URL_TEMPLATE: &str = "https://corretto.aws/downloads/resources/{version}/amazon-corretto-{version}-{platform}-{arch}.tar.gz"; const CORRETTO_WINDOWS_URL_TEMPLATE: &str = "https://corretto.aws/downloads/resources/{version}/amazon-corretto-{version}-{platform}-{arch}-jdk.zip"; fn build_corretto_url(version: &String, platform: &String, arch: &String) -> String { - match zed::current_platform().0 { - Os::Windows => CORRETTO_WINDOWS_URL_TEMPLATE - .replace("{version}", version) - .replace("{platform}", platform) - .replace("{arch}", arch), - _ => CORRETTO_UNIX_URL_TEMPLATE - .replace("{version}", version) - .replace("{platform}", platform) - .replace("{arch}", arch), - } + let template = match zed::current_platform().0 { + Os::Windows => CORRETTO_WINDOWS_URL_TEMPLATE, + _ => CORRETTO_UNIX_URL_TEMPLATE, + }; + + template + .replace("{version}", version) + .replace("{platform}", platform) + .replace("{arch}", arch) } // For now keep in this file as they are not used anywhere else // otherwise move to util -pub fn get_architecture() -> zed::Result { +fn get_architecture() -> zed::Result { match zed::current_platform() { (_, Architecture::Aarch64) => Ok("aarch64".to_string()), (_, Architecture::X86) => Ok("x86".to_string()), @@ -38,7 +38,7 @@ pub fn get_architecture() -> zed::Result { } } -pub fn get_platform() -> zed::Result { +fn get_platform() -> zed::Result { match zed::current_platform() { (Os::Mac, _) => Ok("macosx".to_string()), (Os::Linux, _) => Ok("linux".to_string()), @@ -92,10 +92,35 @@ pub fn try_to_fetch_and_install_latest_jdk( let _ = remove_all_files_except(jdk_path, version.as_str()); } - let exec_path = match current_platform().0 { - Os::Mac => install_path.join("Contents/Home/bin"), - _ => install_path.join("bin"), + // Depending on the platform the name of the extract dir might differ + // Rather than hard coding, extract it dynamically + let extracted_dir = get_extracted_dir(&install_path)?; + + Ok(install_path + .join(extracted_dir) + .join(match current_platform().0 { + Os::Mac => "Contents/Home/bin", + _ => "bin", + })) +} + +fn get_extracted_dir(path: &PathBuf) -> zed::Result { + let Ok(mut entries) = path.read_dir() else { + return Err(JDK_DIR_ERROR.to_string()); }; - return Ok(exec_path.join(get_java_exec_name())); + match entries.find_map(|entry| { + let entry = entry.ok()?; + let file_name = entry.file_name(); + let name_str = file_name.to_string_lossy().to_string(); + + if name_str.contains("jdk") || name_str.contains("corretto") { + Some(name_str) + } else { + None + } + }) { + Some(dir_path) => Ok(dir_path), + None => Err(NO_JDK_DIR_ERROR.to_string()), + } } diff --git a/src/util.rs b/src/util.rs index fb9ecbc..02c96cc 100644 --- a/src/util.rs +++ b/src/util.rs @@ -90,31 +90,33 @@ pub fn get_java_executable( let java_executable_filename = get_java_exec_name(); // Get executable from $JAVA_HOME - if let Some(java_home) = get_java_home(configuration, worktree) { - let java_executable = PathBuf::from(java_home) - .join("bin") - .join(java_executable_filename); - return Ok(java_executable); - } - // If we can't, try to get it from $PATH - if let Some(java_home) = worktree.which(java_executable_filename.as_str()) { - return Ok(PathBuf::from(java_home)); - } + // if let Some(java_home) = get_java_home(configuration, worktree) { + // let java_executable = PathBuf::from(java_home) + // .join("bin") + // .join(java_executable_filename); + // return Ok(java_executable); + // } + // // If we can't, try to get it from $PATH + // if let Some(java_home) = worktree.which(java_executable_filename.as_str()) { + // return Ok(PathBuf::from(java_home)); + // } // If the user has set the option, retrieve the latest version of Corretto (OpenJDK) if is_java_autodownload(configuration) { - try_to_fetch_and_install_latest_jdk(&language_server_id)?; + println!("We're in"); + return Ok(try_to_fetch_and_install_latest_jdk(&language_server_id)? + .join(java_executable_filename)); } Err(JAVA_EXEC_NOT_FOUND_ERROR.to_string()) } -/// Retrieve the executable for Java on this platform +/// Retrieve the executable name for Java on this platform /// /// # Returns /// /// Returns the executable java name -pub fn get_java_exec_name() -> String { +fn get_java_exec_name() -> String { match current_platform().0 { Os::Windows => "java.exe".to_string(), _ => "java".to_string(), From 7274953ed449baadcf4276a355db18c03844e9f3 Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 18:22:52 +0100 Subject: [PATCH 3/8] Restore file after testing --- src/util.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/util.rs b/src/util.rs index 02c96cc..abc6d79 100644 --- a/src/util.rs +++ b/src/util.rs @@ -90,20 +90,19 @@ pub fn get_java_executable( let java_executable_filename = get_java_exec_name(); // Get executable from $JAVA_HOME - // if let Some(java_home) = get_java_home(configuration, worktree) { - // let java_executable = PathBuf::from(java_home) - // .join("bin") - // .join(java_executable_filename); - // return Ok(java_executable); - // } - // // If we can't, try to get it from $PATH - // if let Some(java_home) = worktree.which(java_executable_filename.as_str()) { - // return Ok(PathBuf::from(java_home)); - // } + if let Some(java_home) = get_java_home(configuration, worktree) { + let java_executable = PathBuf::from(java_home) + .join("bin") + .join(java_executable_filename); + return Ok(java_executable); + } + // If we can't, try to get it from $PATH + if let Some(java_home) = worktree.which(java_executable_filename.as_str()) { + return Ok(PathBuf::from(java_home)); + } // If the user has set the option, retrieve the latest version of Corretto (OpenJDK) if is_java_autodownload(configuration) { - println!("We're in"); return Ok(try_to_fetch_and_install_latest_jdk(&language_server_id)? .join(java_executable_filename)); } From 00d8a57354d29b58b13790442248abe90ecfb8e1 Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 18:24:47 +0100 Subject: [PATCH 4/8] Clippy fix --- src/jdk.rs | 6 +++--- src/util.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jdk.rs b/src/jdk.rs index 7a3cea1..6f93e3d 100644 --- a/src/jdk.rs +++ b/src/jdk.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use zed_extension_api::{ self as zed, Architecture, DownloadedFileType, LanguageServerId, @@ -16,7 +16,7 @@ const CORRETTO_REPO: &str = "corretto/corretto-25"; const CORRETTO_UNIX_URL_TEMPLATE: &str = "https://corretto.aws/downloads/resources/{version}/amazon-corretto-{version}-{platform}-{arch}.tar.gz"; const CORRETTO_WINDOWS_URL_TEMPLATE: &str = "https://corretto.aws/downloads/resources/{version}/amazon-corretto-{version}-{platform}-{arch}-jdk.zip"; -fn build_corretto_url(version: &String, platform: &String, arch: &String) -> String { +fn build_corretto_url(version: &str, platform: &str, arch: &str) -> String { let template = match zed::current_platform().0 { Os::Windows => CORRETTO_WINDOWS_URL_TEMPLATE, _ => CORRETTO_UNIX_URL_TEMPLATE, @@ -104,7 +104,7 @@ pub fn try_to_fetch_and_install_latest_jdk( })) } -fn get_extracted_dir(path: &PathBuf) -> zed::Result { +fn get_extracted_dir(path: &Path) -> zed::Result { let Ok(mut entries) = path.read_dir() else { return Err(JDK_DIR_ERROR.to_string()); }; diff --git a/src/util.rs b/src/util.rs index abc6d79..ce6e66e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -103,7 +103,7 @@ pub fn get_java_executable( // If the user has set the option, retrieve the latest version of Corretto (OpenJDK) if is_java_autodownload(configuration) { - return Ok(try_to_fetch_and_install_latest_jdk(&language_server_id)? + return Ok(try_to_fetch_and_install_latest_jdk(language_server_id)? .join(java_executable_filename)); } From 1797d899c66281679c6cfbaed0fbee28ec2df5f1 Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 18:35:21 +0100 Subject: [PATCH 5/8] Retrieve JDK when major version below requirements --- src/jdtls.rs | 19 ++++++++++++++----- src/util.rs | 7 ++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/jdtls.rs b/src/jdtls.rs index 6f2de39..34c3bca 100644 --- a/src/jdtls.rs +++ b/src/jdtls.rs @@ -15,9 +15,13 @@ use zed_extension_api::{ set_language_server_installation_status, }; -use crate::util::{ - get_curr_dir, get_java_executable, get_java_major_version, path_to_string, - remove_all_files_except, +use crate::{ + config::is_java_autodownload, + jdk::try_to_fetch_and_install_latest_jdk, + util::{ + get_curr_dir, get_java_exec_name, get_java_executable, get_java_major_version, + path_to_string, remove_all_files_except, + }, }; const JDTLS_INSTALL_PATH: &str = "jdtls"; @@ -38,10 +42,15 @@ pub fn build_jdtls_launch_args( return Ok(vec![jdtls_launcher]); } - let java_executable = get_java_executable(configuration, worktree, language_server_id)?; + let mut java_executable = get_java_executable(configuration, worktree, language_server_id)?; let java_major_version = get_java_major_version(&java_executable)?; if java_major_version < 21 { - return Err(JAVA_VERSION_ERROR.to_string()); + if is_java_autodownload(configuration) { + java_executable = + try_to_fetch_and_install_latest_jdk(language_server_id)?.join(get_java_exec_name()); + } else { + return Err(JAVA_VERSION_ERROR.to_string()); + } } let extension_workdir = get_curr_dir()?; diff --git a/src/util.rs b/src/util.rs index ce6e66e..adc8b62 100644 --- a/src/util.rs +++ b/src/util.rs @@ -103,8 +103,9 @@ pub fn get_java_executable( // If the user has set the option, retrieve the latest version of Corretto (OpenJDK) if is_java_autodownload(configuration) { - return Ok(try_to_fetch_and_install_latest_jdk(language_server_id)? - .join(java_executable_filename)); + return Ok( + try_to_fetch_and_install_latest_jdk(language_server_id)?.join(java_executable_filename) + ); } Err(JAVA_EXEC_NOT_FOUND_ERROR.to_string()) @@ -115,7 +116,7 @@ pub fn get_java_executable( /// # Returns /// /// Returns the executable java name -fn get_java_exec_name() -> String { +pub fn get_java_exec_name() -> String { match current_platform().0 { Os::Windows => "java.exe".to_string(), _ => "java".to_string(), From e33ff486402597e5be0bf751463c07026bce6b90 Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 18:41:54 +0100 Subject: [PATCH 6/8] Update README with auto download entry --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 50b254d..4eb028a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Install the extension via Zeds extension manager. It should work out of the box - To support [Lombok](https://projectlombok.org/), the lombok-jar must be downloaded and registered as a Java-Agent when launching JDTLS. By default the extension automatically takes care of that, but in case you don't want that you can set the `lombok_support` configuration-option to `false`. +- The option to let the extension automatically download a version of OpenJDK can be enabled by setting `jdk_auto_download` to `true`. When enabled, the extension will only download a JDK if no valid java_home is provided or if the specified one does not meet the minimum version requirement. User-provided JDKs **always** take precedence. + Here is a common `settings.json` including the above mentioned configurations: ```jsonc @@ -22,6 +24,7 @@ Here is a common `settings.json` including the above mentioned configurations: "settings": { "java_home": "/path/to/your/JDK21+", "lombok_support": true, + "jdk_auto_download": false } } } From 98e4d78d764684ae33cd5a610ea1b223af5ad00a Mon Sep 17 00:00:00 2001 From: Riccardo Strina Date: Sun, 2 Nov 2025 18:48:51 +0100 Subject: [PATCH 7/8] Adjust comments --- src/jdk.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jdk.rs b/src/jdk.rs index 6f93e3d..0e128c3 100644 --- a/src/jdk.rs +++ b/src/jdk.rs @@ -68,8 +68,6 @@ pub fn try_to_fetch_and_install_latest_jdk( &LanguageServerInstallationStatus::CheckingForUpdate, ); - // windows and linux have bin as soon as extracted - // macos instead is under Contents/Home/bin if !install_path.exists() { set_language_server_installation_status( language_server_id, @@ -92,7 +90,7 @@ pub fn try_to_fetch_and_install_latest_jdk( let _ = remove_all_files_except(jdk_path, version.as_str()); } - // Depending on the platform the name of the extract dir might differ + // Depending on the platform the name of the extracted dir might differ // Rather than hard coding, extract it dynamically let extracted_dir = get_extracted_dir(&install_path)?; From a7eeba342c6e107c9151abcbb1c18472439efee1 Mon Sep 17 00:00:00 2001 From: Riccardo Strina <85676009+tartarughina@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:41:10 +0100 Subject: [PATCH 8/8] Update JAVA_VERSION_ERROR to reflect latest changes --- src/jdtls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdtls.rs b/src/jdtls.rs index 34c3bca..200446b 100644 --- a/src/jdtls.rs +++ b/src/jdtls.rs @@ -29,7 +29,7 @@ const LOMBOK_INSTALL_PATH: &str = "lombok"; // Errors -const JAVA_VERSION_ERROR: &str = "JDTLS requires at least Java 21. If you need to run a JVM < 21, you can specify a different one for JDTLS to use by specifying lsp.jdtls.settings.java.home in the settings"; +const JAVA_VERSION_ERROR: &str = "JDTLS requires at least Java version 21 to run. You can either specify a different JDK to use by configuring lsp.jdtls.settings.java_home to point to a different JDK, or set lsp.jdtls.settings.jdk_auto_download to true to let the extension automatically download one for you."; pub fn build_jdtls_launch_args( jdtls_path: &PathBuf,