diff --git a/src/debugger.rs b/src/debugger.rs index 4b46492..566889c 100644 --- a/src/debugger.rs +++ b/src/debugger.rs @@ -128,7 +128,9 @@ impl Debugger { configuration, find_latest_local_debugger(), DEBUGGER_INSTALL_PATH, - )? { + ) + .map_err(|err| format!("Failed to resolve debugger installation: {err}"))? + { self.plugin_path = Some(path.clone()); return Ok(path); } @@ -153,11 +155,13 @@ impl Debugger { return Ok(path.clone()); } - create_path_if_not_exists(prefix)?; + create_path_if_not_exists(prefix) + .map_err(|err| format!("Failed to create debugger directory '{prefix}': {err}"))?; download_file( JAVA_DEBUG_PLUGIN_FORK_URL, - &path_to_string(jar_path.clone())?, + &path_to_string(jar_path.clone()) + .map_err(|err| format!("Invalid debugger jar path {jar_path:?}: {err}"))?, DownloadedFileType::Uncompressed, ) .map_err(|err| { @@ -230,7 +234,7 @@ impl Debugger { } let xml = String::from_utf8(res?.body).map_err(|err| { - format!("could not get string from maven metadata response body: {err}") + format!("Failed to get string from Maven metadata response body: {err}") })?; let start_tag = ""; @@ -240,7 +244,9 @@ impl Debugger { .split_once(start_tag) .and_then(|(_, rest)| rest.split_once(end_tag)) .map(|(content, _)| content.trim()) - .ok_or(format!("Failed to parse maven-metadata.xml response {xml}"))?; + .ok_or(format!( + "Failed to parse maven-metadata.xml response: {xml}" + ))?; let artifact = "com.microsoft.java.debug.plugin"; @@ -267,7 +273,7 @@ impl Debugger { &path_to_string(&jar_path)?, DownloadedFileType::Uncompressed, ) - .map_err(|err| format!("Failed to download {url} {err}"))?; + .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); @@ -278,10 +284,15 @@ impl Debugger { } pub fn start_session(&self) -> zed::Result { - let port = self.lsp.get()?.request::( - "workspace/executeCommand", - json!({ "command": "vscode.java.startDebugSession" }), - )?; + let port = self + .lsp + .get() + .map_err(|err| format!("Failed to acquire LSP client lock: {err}"))? + .request::( + "workspace/executeCommand", + json!({ "command": "vscode.java.startDebugSession" }), + ) + .map_err(|err| format!("Failed to start debug session via LSP: {err}"))?; Ok(TcpArgumentsTemplate { host: None, @@ -292,7 +303,7 @@ impl Debugger { pub fn inject_config(&self, worktree: &Worktree, config_string: String) -> zed::Result { let config: Value = serde_json::from_str(&config_string) - .map_err(|err| format!("Failed to parse debug config {err}"))?; + .map_err(|err| format!("Failed to parse debug config: {err}"))?; if config .get("request") @@ -303,7 +314,7 @@ impl Debugger { } let mut config = serde_json::from_value::(config) - .map_err(|err| format!("Failed to parse java debug config {err}"))?; + .map_err(|err| format!("Failed to parse Java debug config: {err}"))?; let workspace_folder = worktree.root_path(); @@ -316,8 +327,10 @@ impl Debugger { let entries = self .lsp - .get()? - .resolve_main_class(arguments)? + .get() + .map_err(|err| format!("Failed to acquire LSP client lock: {err}"))? + .resolve_main_class(arguments) + .map_err(|err| format!("Failed to resolve main class: {err}"))? .into_iter() .filter(|entry| { config @@ -369,7 +382,12 @@ impl Debugger { let arguments = vec![main_class.clone(), project_name.clone(), scope.clone()]; - let result = self.lsp.get()?.resolve_class_path(arguments)?; + let result = self + .lsp + .get() + .map_err(|err| format!("Failed to acquire LSP client lock: {err}"))? + .resolve_class_path(arguments) + .map_err(|err| format!("Failed to resolve classpath: {err}"))?; for resolved in result { classpaths.extend(resolved); @@ -387,7 +405,7 @@ impl Debugger { config.cwd = config.cwd.or(Some(workspace_folder.to_string())); let config = serde_json::to_string(&config) - .map_err(|err| format!("Failed to stringify debug config {err}"))? + .map_err(|err| format!("Failed to stringify debug config: {err}"))? .replace("${workspaceFolder}", &workspace_folder); Ok(config) @@ -397,14 +415,15 @@ impl Debugger { &self, initialization_options: Option, ) -> zed::Result { - let current_dir = get_curr_dir()?; + let current_dir = get_curr_dir() + .map_err(|err| format!("Failed to get current directory for debugger plugin: {err}"))?; let canonical_path = Value::String( current_dir .join( self.plugin_path .as_ref() - .ok_or("Debugger is not loaded yet")?, + .ok_or("Debugger plugin path not set")?, ) .to_string_lossy() .to_string(), diff --git a/src/java.rs b/src/java.rs index 5adc09d..76dc8e2 100644 --- a/src/java.rs +++ b/src/java.rs @@ -173,7 +173,11 @@ impl Extension for Java { } if self.integrations.is_some() { - self.lsp()?.switch_workspace(worktree.root_path())?; + self.lsp()? + .switch_workspace(worktree.root_path()) + .map_err(|err| { + format!("Failed to switch LSP workspace for debug adapter: {err}") + })?; } Ok(DebugAdapterBinary { @@ -182,15 +186,22 @@ impl Extension for Java { cwd: Some(worktree.root_path()), envs: vec![], request_args: StartDebuggingRequestArguments { - request: self.dap_request_kind( - adapter_name, - Value::from_str(config.config.as_str()) - .map_err(|e| format!("Invalid JSON configuration: {e}"))?, - )?, - configuration: self.debugger()?.inject_config(worktree, config.config)?, + request: self + .dap_request_kind( + adapter_name, + Value::from_str(config.config.as_str()) + .map_err(|err| format!("Invalid JSON configuration: {err}"))?, + ) + .map_err(|err| format!("Failed to determine debug request kind: {err}"))?, + configuration: self + .debugger()? + .inject_config(worktree, config.config) + .map_err(|err| format!("Failed to inject debug configuration: {err}"))?, }, connection: Some(zed::resolve_tcp_template( - self.debugger()?.start_session()?, + self.debugger()? + .start_session() + .map_err(|err| format!("Failed to start debug session: {err}"))?, )?), }) } @@ -245,7 +256,11 @@ impl Extension for Java { Ok(zed::DebugScenario { adapter: config.adapter, build: None, - tcp_connection: Some(self.debugger()?.start_session()?), + tcp_connection: Some( + self.debugger()? + .start_session() + .map_err(|err| format!("Failed to start debug session: {err}"))?, + ), label: "Attach to Java process".to_string(), config: debug_config.to_string(), }) @@ -263,7 +278,7 @@ impl Extension for Java { worktree: &Worktree, ) -> zed::Result { let current_dir = - env::current_dir().map_err(|err| format!("could not get current dir: {err}"))?; + env::current_dir().map_err(|err| format!("Failed to get current directory: {err}"))?; let configuration = self.language_server_workspace_configuration(language_server_id, worktree)?; @@ -279,14 +294,17 @@ impl Extension for Java { "--input-type=module".to_string(), "-e".to_string(), PROXY_FILE.to_string(), - path_to_string(current_dir.clone())?, + path_to_string(current_dir.clone()) + .map_err(|err| format!("Failed to convert current directory to string: {err}"))?, ]; // Add lombok as javaagent if settings.java.jdt.ls.lombokSupport.enabled is true let lombok_jvm_arg = if is_lombok_enabled(&configuration) { - let lombok_jar_path = - self.lombok_jar_path(language_server_id, &configuration, worktree)?; - let canonical_lombok_jar_path = path_to_string(current_dir.join(lombok_jar_path))?; + let lombok_jar_path = self + .lombok_jar_path(language_server_id, &configuration, worktree) + .map_err(|err| format!("Failed to get Lombok jar path: {err}"))?; + let canonical_lombok_jar_path = path_to_string(current_dir.join(lombok_jar_path)) + .map_err(|err| format!("Failed to convert Lombok jar path to string: {err}"))?; Some(format!("-javaagent:{canonical_lombok_jar_path}")) } else { @@ -309,13 +327,18 @@ impl Extension for Java { } } else { // otherwise we launch ourselves - args.extend(build_jdtls_launch_args( - &self.language_server_binary_path(language_server_id, &configuration)?, - &configuration, - worktree, - lombok_jvm_arg.into_iter().collect(), - language_server_id, - )?); + args.extend( + build_jdtls_launch_args( + &self + .language_server_binary_path(language_server_id, &configuration) + .map_err(|err| format!("Failed to get JDTLS binary path: {err}"))?, + &configuration, + worktree, + lombok_jvm_arg.into_iter().collect(), + language_server_id, + ) + .map_err(|err| format!("Failed to build JDTLS launch arguments: {err}"))?, + ); } // download debugger if not exists @@ -326,10 +349,13 @@ impl Extension for Java { println!("Failed to download debugger: {err}"); }; - self.lsp()?.switch_workspace(worktree.root_path())?; + self.lsp()? + .switch_workspace(worktree.root_path()) + .map_err(|err| format!("Failed to switch LSP workspace: {err}"))?; Ok(zed::Command { - command: zed::node_binary_path()?, + command: zed::node_binary_path() + .map_err(|err| format!("Failed to get Node.js binary path: {err}"))?, args, env, }) @@ -341,14 +367,25 @@ impl Extension for Java { worktree: &Worktree, ) -> zed::Result> { if self.integrations.is_some() { - self.lsp()?.switch_workspace(worktree.root_path())?; + self.lsp()? + .switch_workspace(worktree.root_path()) + .map_err(|err| { + format!("Failed to switch LSP workspace for initialization: {err}") + })?; } let options = LspSettings::for_worktree(language_server_id.as_ref(), worktree) - .map(|lsp_settings| lsp_settings.initialization_options)?; + .map(|lsp_settings| lsp_settings.initialization_options) + .map_err(|err| format!("Failed to get LSP settings for worktree: {err}"))?; if self.debugger().is_ok_and(|v| v.loaded()) { - return Ok(Some(self.debugger()?.inject_plugin_into_options(options)?)); + return Ok(Some( + self.debugger()? + .inject_plugin_into_options(options) + .map_err(|err| { + format!("Failed to inject debugger plugin into options: {err}") + })?, + )); } Ok(options) diff --git a/src/jdk.rs b/src/jdk.rs index bb0dbb4..a844c9a 100644 --- a/src/jdk.rs +++ b/src/jdk.rs @@ -71,11 +71,14 @@ pub fn try_to_fetch_and_install_latest_jdk( language_server_id: &LanguageServerId, configuration: &Option, ) -> zed::Result { - let jdk_path = get_curr_dir()?.join(JDK_INSTALL_PATH); + let jdk_path = get_curr_dir() + .map_err(|err| format!("Failed to get current directory for JDK installation: {err}"))? + .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)? + should_use_local_or_download(configuration, find_latest_local_jdk(), JDK_INSTALL_PATH) + .map_err(|err| format!("Failed to resolve JDK installation: {err}"))? { return get_jdk_bin_path(&path); } @@ -92,7 +95,8 @@ pub fn try_to_fetch_and_install_latest_jdk( require_assets: false, pre_release: false, }, - )? + ) + .map_err(|err| format!("Failed to fetch latest Corretto release from {CORRETTO_REPO}: {err}"))? .version; let install_path = jdk_path.join(&version); @@ -103,17 +107,23 @@ pub fn try_to_fetch_and_install_latest_jdk( &LanguageServerInstallationStatus::Downloading, ); - let platform = get_platform()?; - let arch = get_architecture()?; + let platform = get_platform() + .map_err(|err| format!("Failed to detect platform for JDK download: {err}"))?; + let arch = get_architecture() + .map_err(|err| format!("Failed to detect architecture for JDK download: {err}"))?; + let download_url = build_corretto_url(&version, &platform, &arch); download_file( - build_corretto_url(&version, &platform, &arch).as_str(), - path_to_string(install_path.clone())?.as_str(), + download_url.as_str(), + path_to_string(install_path.clone()) + .map_err(|err| format!("Invalid JDK install path {install_path:?}: {err}"))? + .as_str(), match zed::current_platform().0 { Os::Windows => DownloadedFileType::Zip, _ => DownloadedFileType::GzipTar, }, - )?; + ) + .map_err(|err| format!("Failed to download Corretto JDK from {download_url}: {err}"))?; // Remove older versions let _ = remove_all_files_except(&jdk_path, version.as_str()); @@ -128,7 +138,8 @@ pub fn try_to_fetch_and_install_latest_jdk( fn get_jdk_bin_path(install_path: &Path) -> zed::Result { // 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) + .map_err(|err| format!("Failed to find JDK directory in {install_path:?}: {err}"))?; Ok(install_path .join(extracted_dir) diff --git a/src/jdtls.rs b/src/jdtls.rs index 5a429af..18972a1 100644 --- a/src/jdtls.rs +++ b/src/jdtls.rs @@ -45,25 +45,32 @@ pub fn build_jdtls_launch_args( return Ok(vec![jdtls_launcher]); } - let mut java_executable = get_java_executable(configuration, worktree, language_server_id)?; - let java_major_version = get_java_major_version(&java_executable)?; + let mut java_executable = get_java_executable(configuration, worktree, language_server_id) + .map_err(|err| format!("Failed to locate Java executable for JDTLS: {err}"))?; + let java_major_version = get_java_major_version(&java_executable) + .map_err(|err| format!("Failed to determine Java version: {err}"))?; if java_major_version < 21 { if is_java_autodownload(configuration) { java_executable = - try_to_fetch_and_install_latest_jdk(language_server_id, configuration)? + try_to_fetch_and_install_latest_jdk(language_server_id, configuration) + .map_err(|err| format!("Failed to auto-download JDK for JDTLS: {err}"))? .join(get_java_exec_name()); } else { return Err(JAVA_VERSION_ERROR.to_string()); } } - let extension_workdir = get_curr_dir()?; + let extension_workdir = get_curr_dir() + .map_err(|err| format!("Failed to get extension working directory: {err}"))?; let jdtls_base_path = extension_workdir.join(jdtls_path); let shared_config_path = get_shared_config_path(&jdtls_base_path); - let jar_path = find_equinox_launcher(&jdtls_base_path)?; - let jdtls_data_path = get_jdtls_data_path(worktree)?; + let jar_path = find_equinox_launcher(&jdtls_base_path).map_err(|err| { + format!("Failed to find JDTLS equinox launcher in {jdtls_base_path:?}: {err}") + })?; + let jdtls_data_path = get_jdtls_data_path(worktree) + .map_err(|err| format!("Failed to determine JDTLS data path: {err}"))?; let mut args = vec![ path_to_string(java_executable)?, @@ -159,7 +166,8 @@ pub fn try_to_fetch_and_install_latest_jdtls( ) -> zed::Result { // Use local installation if update mode requires it if let Some(path) = - should_use_local_or_download(configuration, find_latest_local_jdtls(), JDTLS_INSTALL_PATH)? + should_use_local_or_download(configuration, find_latest_local_jdtls(), JDTLS_INSTALL_PATH) + .map_err(|err| format!("Failed to resolve JDTLS installation: {err}"))? { return Ok(path); } @@ -170,7 +178,8 @@ pub fn try_to_fetch_and_install_latest_jdtls( &LanguageServerInstallationStatus::CheckingForUpdate, ); - let (last, second_last) = get_latest_versions_from_tag(JDTLS_REPO)?; + let (last, second_last) = get_latest_versions_from_tag(JDTLS_REPO) + .map_err(|err| format!("Failed to fetch JDTLS versions from {JDTLS_REPO}: {err}"))?; let (latest_version, latest_version_build) = download_jdtls_milestone(last.as_ref()) .map_or_else( @@ -198,14 +207,23 @@ pub fn try_to_fetch_and_install_latest_jdtls( language_server_id, &LanguageServerInstallationStatus::Downloading, ); + let download_url = format!( + "https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/{latest_version}/{latest_version_build}" + ); download_file( - &format!( - "https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/{latest_version}/{latest_version_build}" - ), - path_to_string(build_path.clone())?.as_str(), + &download_url, + path_to_string(build_path.clone()) + .map_err(|err| format!("Invalid JDTLS build path {build_path:?}: {err}"))? + .as_str(), DownloadedFileType::GzipTar, - )?; - make_file_executable(path_to_string(binary_path)?.as_str())?; + ) + .map_err(|err| format!("Failed to download JDTLS from {download_url}: {err}"))?; + make_file_executable( + path_to_string(&binary_path) + .map_err(|err| format!("Invalid JDTLS binary path {binary_path:?}: {err}"))? + .as_str(), + ) + .map_err(|err| format!("Failed to make JDTLS executable at {binary_path:?}: {err}"))?; // ...and delete other versions let _ = remove_all_files_except(prefix, build_directory.as_str()); @@ -227,7 +245,9 @@ pub fn try_to_fetch_and_install_latest_lombok( configuration, find_latest_local_lombok(), LOMBOK_INSTALL_PATH, - )? { + ) + .map_err(|err| format!("Failed to resolve Lombok installation: {err}"))? + { return Ok(path); } @@ -237,7 +257,8 @@ pub fn try_to_fetch_and_install_latest_lombok( &LanguageServerInstallationStatus::CheckingForUpdate, ); - let (latest_version, _) = get_latest_versions_from_tag(LOMBOK_REPO)?; + let (latest_version, _) = get_latest_versions_from_tag(LOMBOK_REPO) + .map_err(|err| format!("Failed to fetch Lombok versions from {LOMBOK_REPO}: {err}"))?; let prefix = LOMBOK_INSTALL_PATH; let jar_name = format!("lombok-{latest_version}.jar"); let jar_path = Path::new(prefix).join(&jar_name); @@ -250,12 +271,17 @@ pub fn try_to_fetch_and_install_latest_lombok( language_server_id, &LanguageServerInstallationStatus::Downloading, ); - create_path_if_not_exists(prefix)?; + create_path_if_not_exists(prefix) + .map_err(|err| format!("Failed to create Lombok directory '{prefix}': {err}"))?; + let download_url = format!("https://projectlombok.org/downloads/{jar_name}"); download_file( - &format!("https://projectlombok.org/downloads/{jar_name}"), - path_to_string(jar_path.clone())?.as_str(), + &download_url, + path_to_string(jar_path.clone()) + .map_err(|err| format!("Invalid Lombok jar path {jar_path:?}: {err}"))? + .as_str(), DownloadedFileType::Uncompressed, - )?; + ) + .map_err(|err| format!("Failed to download Lombok from {download_url}: {err}"))?; // ...and delete other versions @@ -279,12 +305,10 @@ fn download_jdtls_milestone(version: &str) -> zed::Result { )) .build()?, ) - .map_err(|err| format!("failed to get latest version's build: {err}"))? + .map_err(|err| format!("Failed to get latest version's build: {err}"))? .body, ) - .map_err(|err| { - format!("attempt to get latest version's build resulted in a malformed response: {err}") - }) + .map_err(|err| format!("Failed to get latest version's build (malformed response): {err}")) } fn find_equinox_launcher(jdtls_base_directory: &Path) -> Result { @@ -298,7 +322,7 @@ fn find_equinox_launcher(jdtls_base_directory: &Path) -> Result // else get the first file that matches the glob 'org.eclipse.equinox.launcher_*.jar' let entries = - read_dir(&plugins_dir).map_err(|e| format!("Failed to read plugins directory: {e}"))?; + read_dir(&plugins_dir).map_err(|err| format!("Failed to read plugins directory: {err}"))?; entries .filter_map(Result::ok) diff --git a/src/lsp.rs b/src/lsp.rs index de1fcbd..5c1b804 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -35,14 +35,14 @@ impl LspWrapper { pub fn get(&self) -> zed::Result> { self.0 .read() - .map_err(|err| format!("LspClient RwLock poisoned during read {err}")) + .map_err(|err| format!("LspClient RwLock poisoned during read: {err}")) } pub fn switch_workspace(&self, workspace: String) -> zed::Result<()> { let mut lock = self .0 .write() - .map_err(|err| format!("LspClient RwLock poisoned during read {err}"))?; + .map_err(|err| format!("LspClient RwLock poisoned during read: {err}"))?; lock.workspace = workspace; @@ -86,9 +86,9 @@ impl LspClient { } fs::read_to_string(port_path) - .map_err(|e| format!("Failed to read a lsp proxy port from file {e}"))? + .map_err(|err| format!("Failed to read LSP proxy port from file: {err}"))? .parse::() - .map_err(|e| format!("Failed to read a lsp proxy port, file corrupted {e}"))? + .map_err(|err| format!("Failed to parse LSP proxy port (file corrupted): {err}"))? }; let mut body = Map::new(); @@ -102,10 +102,10 @@ impl LspClient { .body(Value::Object(body).to_string()) .build()?, ) - .map_err(|e| format!("Failed to send request to lsp proxy {e}"))?; + .map_err(|err| format!("Failed to send request to LSP proxy: {err}"))?; let data: LspResponse = serde_json::from_slice(&res.body) - .map_err(|e| format!("Failed to parse response from lsp proxy {e}"))?; + .map_err(|err| format!("Failed to parse response from LSP proxy: {err}"))?; match data { LspResponse::Success { result } => Ok(result), diff --git a/src/util.rs b/src/util.rs index 152de2e..06fa4a3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -95,8 +95,10 @@ pub fn has_checked_once(component_name: &str) -> bool { /// 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()) + create_path_if_not_exists(PathBuf::from(component_name)) + .map_err(|err| format!("Failed to create directory for {component_name}: {err}"))?; + fs::write(&marker_path, version) + .map_err(|err| format!("Failed to write marker file {marker_path:?}: {err}")) } /// Expand ~ on Unix-like systems @@ -216,12 +218,18 @@ pub fn get_java_exec_name() -> String { /// * [`java_executable`] can't be converted into a String /// * No major version can be determined pub fn get_java_major_version(java_executable: &PathBuf) -> zed::Result { - let program = path_to_string(java_executable).map_err(|_| JAVA_EXEC_ERROR.to_string())?; - let output_bytes = Command::new(program).arg("-version").output()?.stderr; - let output = String::from_utf8(output_bytes).map_err(|e| e.to_string())?; + let program = path_to_string(java_executable) + .map_err(|err| format!("{JAVA_EXEC_ERROR} '{java_executable:?}': {err}"))?; + let output_bytes = Command::new(&program) + .arg("-version") + .output() + .map_err(|err| format!("Failed to execute '{program} -version': {err}"))? + .stderr; + let output = String::from_utf8(output_bytes) + .map_err(|err| format!("Invalid UTF-8 in java version output: {err}"))?; - let major_version_regex = - Regex::new(r#"version\s"(?P\d+)(\.\d+\.\d+(_\d+)?)?"#).map_err(|e| e.to_string())?; + let major_version_regex = Regex::new(r#"version\s"(?P\d+)(\.\d+\.\d+(_\d+)?)?"#) + .map_err(|err| format!("Invalid regex for Java version parsing: {err}"))?; let major_version = major_version_regex .captures_iter(&output) .find_map(|c| c.name("major").and_then(|m| m.as_str().parse::().ok()));