From 8afc0282f28e73cf78d1bd7b11d78fd853ae2036 Mon Sep 17 00:00:00 2001 From: Yomain <40139584+yo-main@users.noreply.github.com> Date: Tue, 11 Jul 2023 19:51:04 +0200 Subject: [PATCH] Fix crash when cwd is deleted (#7185) --- Cargo.lock | 1 + helix-core/src/path.rs | 8 +++--- helix-loader/Cargo.toml | 1 + helix-loader/src/lib.rs | 44 ++++++++++++++++++++++++++++++-- helix-lsp/src/lib.rs | 2 +- helix-term/src/application.rs | 2 +- helix-term/src/commands.rs | 23 ++++++++++++++--- helix-term/src/commands/dap.rs | 2 +- helix-term/src/commands/lsp.rs | 2 +- helix-term/src/commands/typed.rs | 18 +++++++------ helix-term/src/ui/mod.rs | 2 +- 11 files changed, 81 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a18a4dd..68d5727f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1260,6 +1260,7 @@ version = "0.6.0" dependencies = [ "anyhow", "cc", + "dunce", "etcetera", "libloading", "log", diff --git a/helix-core/src/path.rs b/helix-core/src/path.rs index efa46c46..85c60255 100644 --- a/helix-core/src/path.rs +++ b/helix-core/src/path.rs @@ -88,7 +88,7 @@ pub fn get_normalized_path(path: &Path) -> PathBuf { pub fn get_canonicalized_path(path: &Path) -> std::io::Result { let path = expand_tilde(path); let path = if path.is_relative() { - std::env::current_dir().map(|current_dir| current_dir.join(path))? + helix_loader::current_working_dir().join(path) } else { path }; @@ -99,9 +99,7 @@ pub fn get_canonicalized_path(path: &Path) -> std::io::Result { pub fn get_relative_path(path: &Path) -> PathBuf { let path = PathBuf::from(path); let path = if path.is_absolute() { - let cwdir = std::env::current_dir() - .map(|path| get_normalized_path(&path)) - .expect("couldn't determine current directory"); + let cwdir = get_normalized_path(&helix_loader::current_working_dir()); get_normalized_path(&path) .strip_prefix(cwdir) .map(PathBuf::from) @@ -142,7 +140,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf { /// ``` /// pub fn get_truncated_path>(path: P) -> PathBuf { - let cwd = std::env::current_dir().unwrap_or_default(); + let cwd = helix_loader::current_working_dir(); let path = path .as_ref() .strip_prefix(cwd) diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml index 3e7fc2e7..903d36c0 100644 --- a/helix-loader/Cargo.toml +++ b/helix-loader/Cargo.toml @@ -29,6 +29,7 @@ which = "4.4" cc = { version = "1" } threadpool = { version = "1.0" } tempfile = "3.6.0" +dunce = "1.0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libloading = "0.8" diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index c51c9c7d..7ff4cada 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -3,9 +3,12 @@ pub mod grammar; use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; use std::path::{Path, PathBuf}; +use std::sync::RwLock; pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH"); +static CWD: RwLock> = RwLock::new(None); + static RUNTIME_DIRS: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(prioritize_runtime_dirs); @@ -13,6 +16,31 @@ static CONFIG_FILE: once_cell::sync::OnceCell = once_cell::sync::OnceCe static LOG_FILE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); +// Get the current working directory. +// This information is managed internally as the call to std::env::current_dir +// might fail if the cwd has been deleted. +pub fn current_working_dir() -> PathBuf { + if let Some(path) = &*CWD.read().unwrap() { + return path.clone(); + } + + let path = std::env::current_dir() + .and_then(dunce::canonicalize) + .expect("Couldn't determine current working directory"); + let mut cwd = CWD.write().unwrap(); + *cwd = Some(path.clone()); + + path +} + +pub fn set_current_working_dir(path: PathBuf) -> std::io::Result<()> { + let path = dunce::canonicalize(path)?; + std::env::set_current_dir(path.clone())?; + let mut cwd = CWD.write().unwrap(); + *cwd = Some(path); + Ok(()) +} + pub fn initialize_config_file(specified_file: Option) { let config_file = specified_file.unwrap_or_else(default_config_file); ensure_parent_dir(&config_file); @@ -217,7 +245,7 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi /// If no workspace was found returns (CWD, true). /// Otherwise (workspace, false) is returned pub fn find_workspace() -> (PathBuf, bool) { - let current_dir = std::env::current_dir().expect("unable to determine current directory"); + let current_dir = current_working_dir(); for ancestor in current_dir.ancestors() { if ancestor.join(".git").exists() || ancestor.join(".helix").exists() { return (ancestor.to_owned(), false); @@ -243,9 +271,21 @@ fn ensure_parent_dir(path: &Path) { mod merge_toml_tests { use std::str; - use super::merge_toml_values; + use super::{current_working_dir, merge_toml_values, set_current_working_dir}; use toml::Value; + #[test] + fn current_dir_is_set() { + let new_path = dunce::canonicalize(std::env::temp_dir()).unwrap(); + let cwd = current_working_dir(); + assert_ne!(cwd, new_path); + + set_current_working_dir(new_path.clone()).expect("Couldn't set new path"); + + let cwd = current_working_dir(); + assert_eq!(cwd, new_path); + } + #[test] fn language_toml_map_merges() { const USER: &str = r#" diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 277a4c28..95c61086 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -931,7 +931,7 @@ pub fn find_lsp_workspace( let mut file = if file.is_absolute() { file.to_path_buf() } else { - let current_dir = std::env::current_dir().expect("unable to determine current directory"); + let current_dir = helix_loader::current_working_dir(); current_dir.join(file) }; file = path::get_normalized_path(&file); diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 4c86a19f..546a57a9 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -163,7 +163,7 @@ impl Application { } else if !args.files.is_empty() { let first = &args.files[0].0; // we know it's not empty if first.is_dir() { - std::env::set_current_dir(first).context("set current dir")?; + helix_loader::set_current_working_dir(first.clone())?; editor.new_file(Action::VerticalSplit); let picker = ui::file_picker(".".into(), &config.load().editor); compositor.push(Box::new(overlaid(picker))); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ae715887..47b1a175 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2080,8 +2080,12 @@ fn global_search(cx: &mut Context) { .binary_detection(BinaryDetection::quit(b'\x00')) .build(); - let search_root = std::env::current_dir() - .expect("Global search error: Failed to get current dir"); + let search_root = helix_loader::current_working_dir(); + if !search_root.exists() { + editor.set_error("Current working directory does not exist"); + return; + } + let dedup_symlinks = file_picker_config.deduplicate_links; let absolute_root = search_root .canonicalize() @@ -2173,7 +2177,9 @@ fn global_search(cx: &mut Context) { let call: job::Callback = Callback::EditorCompositor(Box::new( move |editor: &mut Editor, compositor: &mut Compositor| { if all_matches.is_empty() { - editor.set_status("No matches found"); + if !editor.is_err() { + editor.set_status("No matches found"); + } return; } @@ -2518,6 +2524,10 @@ fn append_mode(cx: &mut Context) { fn file_picker(cx: &mut Context) { let root = find_workspace().0; + if !root.exists() { + cx.editor.set_error("Workspace directory does not exist"); + return; + } let picker = ui::file_picker(root, &cx.editor.config()); cx.push_layer(Box::new(overlaid(picker))); } @@ -2539,7 +2549,12 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) { cx.push_layer(Box::new(overlaid(picker))); } fn file_picker_in_current_directory(cx: &mut Context) { - let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./")); + let cwd = helix_loader::current_working_dir(); + if !cwd.exists() { + cx.editor + .set_error("Current working directory does not exist"); + return; + } let picker = ui::file_picker(cwd, &cx.editor.config()); cx.push_layer(Box::new(overlaid(picker))); } diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 70a5ec21..e26dc08d 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -217,7 +217,7 @@ pub fn dap_start_impl( } } - args.insert("cwd", to_value(std::env::current_dir().unwrap())?); + args.insert("cwd", to_value(helix_loader::current_working_dir())?); let args = to_value(args).unwrap(); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 55153648..145bddd0 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -1033,7 +1033,7 @@ fn goto_impl( locations: Vec, offset_encoding: OffsetEncoding, ) { - let cwdir = std::env::current_dir().unwrap_or_default(); + let cwdir = helix_loader::current_working_dir(); match locations.as_slice() { [location] => { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 94cc33f0..dfc71dfd 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1093,14 +1093,11 @@ fn change_current_directory( .as_ref(), ); - if let Err(e) = std::env::set_current_dir(dir) { - bail!("Couldn't change the current working directory: {}", e); - } + helix_loader::set_current_working_dir(dir)?; - let cwd = std::env::current_dir().context("Couldn't get the new working directory")?; cx.editor.set_status(format!( "Current working directory is now {}", - cwd.display() + helix_loader::current_working_dir().display() )); Ok(()) } @@ -1114,9 +1111,14 @@ fn show_current_directory( return Ok(()); } - let cwd = std::env::current_dir().context("Couldn't get the new working directory")?; - cx.editor - .set_status(format!("Current working directory is {}", cwd.display())); + let cwd = helix_loader::current_working_dir(); + let message = format!("Current working directory is {}", cwd.display()); + + if cwd.exists() { + cx.editor.set_status(message); + } else { + cx.editor.set_error(format!("{} (deleted)", message)); + } Ok(()) } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 155f2435..3359155d 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -472,7 +472,7 @@ pub mod completers { match path.parent() { Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(), // Path::new("h")'s parent is Some("")... - _ => std::env::current_dir().expect("couldn't determine current directory"), + _ => helix_loader::current_working_dir(), } };