Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
config::get_java_debug_jar,
lsp::LspWrapper,
util::{
create_path_if_not_exists, get_curr_dir, path_to_quoted_string,
create_path_if_not_exists, get_curr_dir, mark_checked_once, path_to_quoted_string,
should_use_local_or_download,
},
};
Expand Down Expand Up @@ -124,9 +124,11 @@ impl Debugger {
}

// Use local installation if update mode requires it
if let Some(path) =
should_use_local_or_download(configuration, find_latest_local_debugger(), "debugger")?
{
if let Some(path) = should_use_local_or_download(
configuration,
find_latest_local_debugger(),
DEBUGGER_INSTALL_PATH,
)? {
self.plugin_path = Some(path.clone());
return Ok(path);
}
Expand Down Expand Up @@ -162,6 +164,9 @@ impl Debugger {
format!("Failed to download java-debug fork from {JAVA_DEBUG_PLUGIN_FORK_URL}: {err}")
})?;

// Mark the downloaded version for "Once" mode tracking
let _ = mark_checked_once(DEBUGGER_INSTALL_PATH, latest_version);

self.plugin_path = Some(jar_path.clone());
Ok(jar_path)
}
Expand Down Expand Up @@ -263,6 +268,9 @@ impl Debugger {
DownloadedFileType::Uncompressed,
)
.map_err(|err| format!("Failed to download {url} {err}"))?;

// Mark the downloaded version for "Once" mode tracking
let _ = mark_checked_once(DEBUGGER_INSTALL_PATH, latest_version);
}

self.plugin_path = Some(jar_path.clone());
Expand Down
60 changes: 48 additions & 12 deletions src/jdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ use std::path::{Path, PathBuf};

use zed_extension_api::{
self as zed, Architecture, DownloadedFileType, LanguageServerId,
LanguageServerInstallationStatus, Os, current_platform, download_file,
LanguageServerInstallationStatus, Os, current_platform, download_file, serde_json::Value,
set_language_server_installation_status,
};

use crate::util::{get_curr_dir, path_to_quoted_string, remove_all_files_except};
use crate::util::{
get_curr_dir, mark_checked_once, path_to_quoted_string, remove_all_files_except,
should_use_local_or_download,
};

// Errors
const JDK_DIR_ERROR: &str = "Failed to read into JDK install directory";
Expand All @@ -15,6 +18,7 @@ const NO_JDK_DIR_ERROR: &str = "No match for jdk or corretto in the extracted di
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";
const JDK_INSTALL_PATH: &str = "jdk";

fn build_corretto_url(version: &str, platform: &str, arch: &str) -> String {
let template = match zed::current_platform().0 {
Expand Down Expand Up @@ -46,9 +50,42 @@ fn get_platform() -> zed::Result<String> {
}
}

fn find_latest_local_jdk() -> Option<PathBuf> {
let jdk_path = get_curr_dir().ok()?.join(JDK_INSTALL_PATH);
std::fs::read_dir(&jdk_path)
.ok()?
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| path.is_dir())
.filter_map(|path| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I have the feeling that the filter_map - map - filter - filter_map can be better expressed as a single filter_map, something along the lines of:

std::fs::read_dir(&jdk_path)
    .ok()?
    .filter_map(|entry| {
        let entry = entry.ok()?;
        let metadata = entry.metadata().ok()?; // I think we even avoid the second syscall this way

        if !metadata.is_dir() {
            return None;
        }

        let time = metadata.created().ok()?;
        Some((entry.path(), time))
    })
    .max_by_key(|&(_, time)| time)
    .map(|(path, _)| path)
    ```

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same filter_map - map - filter - filter_map sequence is used also in other places, like in jdtls.rs .
We could have it cleaned throughout the project in a different PR

let created_time = std::fs::metadata(&path)
.and_then(|meta| meta.created())
.ok()?;
Some((path, created_time))
})
.max_by_key(|&(_, time)| time)
.map(|(path, _)| path)
}

pub fn try_to_fetch_and_install_latest_jdk(
language_server_id: &LanguageServerId,
configuration: &Option<Value>,
) -> zed::Result<PathBuf> {
let jdk_path = get_curr_dir()?.join(JDK_INSTALL_PATH);

// Check if we should use local installation based on update mode
if let Some(path) =
should_use_local_or_download(configuration, find_latest_local_jdk(), JDK_INSTALL_PATH)?
{
return get_jdk_bin_path(&path);
}

// Check for updates, if same version is already downloaded skip download
set_language_server_installation_status(
language_server_id,
&LanguageServerInstallationStatus::CheckingForUpdate,
);

let version = zed::latest_github_release(
CORRETTO_REPO,
zed_extension_api::GithubReleaseOptions {
Expand All @@ -58,16 +95,8 @@ pub fn try_to_fetch_and_install_latest_jdk(
)?
.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,
);

if !install_path.exists() {
set_language_server_installation_status(
language_server_id,
Expand All @@ -87,12 +116,19 @@ pub fn try_to_fetch_and_install_latest_jdk(
)?;

// Remove older versions
let _ = remove_all_files_except(jdk_path, version.as_str());
let _ = remove_all_files_except(&jdk_path, version.as_str());

// Mark the downloaded version for "Once" mode tracking
let _ = mark_checked_once(JDK_INSTALL_PATH, &version);
}

get_jdk_bin_path(&install_path)
}

fn get_jdk_bin_path(install_path: &Path) -> zed::Result<PathBuf> {
// 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)?;
let extracted_dir = get_extracted_dir(install_path)?;

Ok(install_path
.join(extracted_dir)
Expand Down
28 changes: 21 additions & 7 deletions src/jdtls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::{
jdk::try_to_fetch_and_install_latest_jdk,
util::{
create_path_if_not_exists, get_curr_dir, get_java_exec_name, get_java_executable,
get_java_major_version, get_latest_versions_from_tag, path_to_quoted_string,
remove_all_files_except, should_use_local_or_download,
get_java_major_version, get_latest_versions_from_tag, mark_checked_once,
path_to_quoted_string, remove_all_files_except, should_use_local_or_download,
},
};

Expand Down Expand Up @@ -50,7 +50,8 @@ pub fn build_jdtls_launch_args(
if java_major_version < 21 {
if is_java_autodownload(configuration) {
java_executable =
try_to_fetch_and_install_latest_jdk(language_server_id)?.join(get_java_exec_name());
try_to_fetch_and_install_latest_jdk(language_server_id, configuration)?
.join(get_java_exec_name());
} else {
return Err(JAVA_VERSION_ERROR.to_string());
}
Expand Down Expand Up @@ -157,12 +158,17 @@ pub fn try_to_fetch_and_install_latest_jdtls(
) -> zed::Result<PathBuf> {
// Use local installation if update mode requires it
if let Some(path) =
should_use_local_or_download(configuration, find_latest_local_jdtls(), "jdtls")?
should_use_local_or_download(configuration, find_latest_local_jdtls(), JDTLS_INSTALL_PATH)?
{
return Ok(path);
}

// Download latest version
set_language_server_installation_status(
language_server_id,
&LanguageServerInstallationStatus::CheckingForUpdate,
);

let (last, second_last) = get_latest_versions_from_tag(JDTLS_REPO)?;

let (latest_version, latest_version_build) = download_jdtls_milestone(last.as_ref())
Expand Down Expand Up @@ -202,6 +208,9 @@ pub fn try_to_fetch_and_install_latest_jdtls(

// ...and delete other versions
let _ = remove_all_files_except(prefix, build_directory.as_str());

// Mark the downloaded version for "Once" mode tracking
let _ = mark_checked_once(JDTLS_INSTALL_PATH, &latest_version);
}

// return jdtls base path
Expand All @@ -213,9 +222,11 @@ pub fn try_to_fetch_and_install_latest_lombok(
configuration: &Option<Value>,
) -> zed::Result<PathBuf> {
// Use local installation if update mode requires it
if let Some(path) =
should_use_local_or_download(configuration, find_latest_local_lombok(), "lombok")?
{
if let Some(path) = should_use_local_or_download(
configuration,
find_latest_local_lombok(),
LOMBOK_INSTALL_PATH,
)? {
return Ok(path);
}

Expand Down Expand Up @@ -248,6 +259,9 @@ pub fn try_to_fetch_and_install_latest_lombok(
// ...and delete other versions

let _ = remove_all_files_except(prefix, jar_name.as_str());

// Mark the downloaded version for "Once" mode tracking
let _ = mark_checked_once(LOMBOK_INSTALL_PATH, &latest_version);
}

// else use it
Expand Down
62 changes: 59 additions & 3 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const TAG_UNEXPECTED_FORMAT_ERROR: &str = "Malformed GitHub tags response";
const PATH_IS_NOT_DIR: &str = "File exists but is not a path";
const NO_LOCAL_INSTALL_NEVER_ERROR: &str =
"Update checks disabled (never) and no local installation found";
const NO_LOCAL_INSTALL_ONCE_ERROR: &str =
"Update check already performed once and no local installation found";

const ONCE_CHECK_MARKER: &str = ".update_checked";

/// Create a Path if it does not exist
///
Expand Down Expand Up @@ -60,6 +64,41 @@ pub fn create_path_if_not_exists<P: AsRef<Path>>(path: P) -> zed::Result<()> {
}
}

/// Check if update check has been performed once for a component
///
/// # Arguments
///
/// * [`component_name`] - The component directory name (e.g., "jdtls", "lombok")
///
/// # Returns
///
/// Returns true if the marker file exists, indicating a check was already performed
pub fn has_checked_once(component_name: &str) -> bool {
PathBuf::from(component_name)
.join(ONCE_CHECK_MARKER)
.exists()
}

/// Mark that an update check has been performed for a component
///
/// # Arguments
///
/// * [`component_name`] - The component directory name (e.g., "jdtls", "lombok")
/// * [`version`] - The version that was downloaded
///
/// # Returns
///
/// Returns Ok(()) if the marker was created successfully
///
/// # Errors
///
/// Returns an error if the directory or marker file could not be created
pub fn mark_checked_once(component_name: &str, version: &str) -> zed::Result<()> {
let marker_path = PathBuf::from(component_name).join(ONCE_CHECK_MARKER);
create_path_if_not_exists(PathBuf::from(component_name))?;
fs::write(marker_path, version).map_err(|e| e.to_string())
}

/// Expand ~ on Unix-like systems
///
/// # Arguments
Expand Down Expand Up @@ -140,7 +179,8 @@ 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)
try_to_fetch_and_install_latest_jdk(language_server_id, configuration)?
.join(java_executable_filename),
);
}

Expand Down Expand Up @@ -253,7 +293,7 @@ fn get_tag_at(github_tags: &Value, index: usize) -> Option<&str> {
/// On Unix, returns the path unquoted since spawn() treats quotes as literals.
fn format_path_for_os(path_str: String, os: Os) -> String {
if os == Os::Windows {
format!("\"{}\"", path_str)
format!("\"{path_str}\"")
} else {
path_str
}
Expand Down Expand Up @@ -350,6 +390,7 @@ pub fn remove_all_files_except<P: AsRef<Path>>(prefix: P, filename: &str) -> zed
///
/// # Errors
/// - Update mode is Never but no local installation found
/// - Update mode is Once and already checked but no local installation found
pub fn should_use_local_or_download(
configuration: &Option<Value>,
local: Option<PathBuf>,
Expand All @@ -362,7 +403,22 @@ pub fn should_use_local_or_download(
"{NO_LOCAL_INSTALL_NEVER_ERROR} for {component_name}"
)),
},
CheckUpdates::Once => Ok(local),
CheckUpdates::Once => {
// If we have a local installation, use it
if let Some(path) = local {
return Ok(Some(path));
}

// If we've already checked once, don't check again
if has_checked_once(component_name) {
return Err(format!(
"{NO_LOCAL_INSTALL_ONCE_ERROR} for {component_name}"
));
}

// First time checking - allow download
Ok(None)
}
CheckUpdates::Always => Ok(None),
}
}
Expand Down