on master help sign in
Fix crash when cwd is deleted (#7185)
8afc028Yomain
Cargo.lock+1
@@ -1260,6 +1260,7 @@ version = "0.6.0"
1260dependencies = [
1260dependencies = [
1261 "anyhow",
1261 "anyhow",
1262 "cc",
1262 "cc",
1263+ "dunce",
1263 "etcetera",
1264 "etcetera",
1264 "libloading",
1265 "libloading",
1265 "log",
1266 "log",
helix-core/src/path.rs+3−5
@@ -88,7 +88,7 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
88pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
88pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
89 let path = expand_tilde(path);
89 let path = expand_tilde(path);
90 let path = if path.is_relative() {
90 let path = if path.is_relative() {
91− std::env::current_dir().map(|current_dir| current_dir.join(path))?
91+ helix_loader::current_working_dir().join(path)
92 } else {
92 } else {
93 path
93 path
94 };
94 };
@@ -99,9 +99,7 @@ pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
99pub fn get_relative_path(path: &Path) -> PathBuf {
99pub fn get_relative_path(path: &Path) -> PathBuf {
100 let path = PathBuf::from(path);
100 let path = PathBuf::from(path);
101 let path = if path.is_absolute() {
101 let path = if path.is_absolute() {
102− let cwdir = std::env::current_dir()
103− .map(|path| get_normalized_path(&path))
102+ let cwdir = get_normalized_path(&helix_loader::current_working_dir());
104− .expect("couldn't determine current directory");
105 get_normalized_path(&path)
103 get_normalized_path(&path)
106 .strip_prefix(cwdir)
104 .strip_prefix(cwdir)
107 .map(PathBuf::from)
105 .map(PathBuf::from)
@@ -142,7 +140,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
142/// ```
140/// ```
143///
141///
144pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
142pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
145− let cwd = std::env::current_dir().unwrap_or_default();
143+ let cwd = helix_loader::current_working_dir();
146 let path = path
144 let path = path
147 .as_ref()
145 .as_ref()
148 .strip_prefix(cwd)
146 .strip_prefix(cwd)
helix-loader/Cargo.toml+1
@@ -29,6 +29,7 @@ which = "4.4"
29cc = { version = "1" }
29cc = { version = "1" }
30threadpool = { version = "1.0" }
30threadpool = { version = "1.0" }
31tempfile = "3.6.0"
31tempfile = "3.6.0"
32+dunce = "1.0.4"
32
33
33[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
34[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
34libloading = "0.8"
35libloading = "0.8"
helix-loader/src/lib.rs+42−2
@@ -3,9 +3,12 @@ pub mod grammar;
3
3
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
5use std::path::{Path, PathBuf};
5use std::path::{Path, PathBuf};
6+use std::sync::RwLock;
6
7
7pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
8pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
8
9
10+static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
11+
9static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
12static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
10 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
13 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
11
14
@@ -13,6 +16,31 @@ static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCe
13
16
14static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
17static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
15
18
19+// Get the current working directory.
20+// This information is managed internally as the call to std::env::current_dir
21+// might fail if the cwd has been deleted.
22+pub fn current_working_dir() -> PathBuf {
23+ if let Some(path) = &*CWD.read().unwrap() {
24+ return path.clone();
25+ }
26+
27+ let path = std::env::current_dir()
28+ .and_then(dunce::canonicalize)
29+ .expect("Couldn't determine current working directory");
30+ let mut cwd = CWD.write().unwrap();
31+ *cwd = Some(path.clone());
32+
33+ path
34+}
35+
36+pub fn set_current_working_dir(path: PathBuf) -> std::io::Result<()> {
37+ let path = dunce::canonicalize(path)?;
38+ std::env::set_current_dir(path.clone())?;
39+ let mut cwd = CWD.write().unwrap();
40+ *cwd = Some(path);
41+ Ok(())
42+}
43+
16pub fn initialize_config_file(specified_file: Option<PathBuf>) {
44pub fn initialize_config_file(specified_file: Option<PathBuf>) {
17 let config_file = specified_file.unwrap_or_else(default_config_file);
45 let config_file = specified_file.unwrap_or_else(default_config_file);
18 ensure_parent_dir(&config_file);
46 ensure_parent_dir(&config_file);
@@ -217,7 +245,7 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
217/// If no workspace was found returns (CWD, true).
245/// If no workspace was found returns (CWD, true).
218/// Otherwise (workspace, false) is returned
246/// Otherwise (workspace, false) is returned
219pub fn find_workspace() -> (PathBuf, bool) {
247pub fn find_workspace() -> (PathBuf, bool) {
220− let current_dir = std::env::current_dir().expect("unable to determine current directory");
248+ let current_dir = current_working_dir();
221 for ancestor in current_dir.ancestors() {
249 for ancestor in current_dir.ancestors() {
222 if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
250 if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
223 return (ancestor.to_owned(), false);
251 return (ancestor.to_owned(), false);
@@ -243,9 +271,21 @@ fn ensure_parent_dir(path: &Path) {
243mod merge_toml_tests {
271mod merge_toml_tests {
244 use std::str;
272 use std::str;
245
273
246− use super::merge_toml_values;
274+ use super::{current_working_dir, merge_toml_values, set_current_working_dir};
247 use toml::Value;
275 use toml::Value;
248
276
277+ #[test]
278+ fn current_dir_is_set() {
279+ let new_path = dunce::canonicalize(std::env::temp_dir()).unwrap();
280+ let cwd = current_working_dir();
281+ assert_ne!(cwd, new_path);
282+
283+ set_current_working_dir(new_path.clone()).expect("Couldn't set new path");
284+
285+ let cwd = current_working_dir();
286+ assert_eq!(cwd, new_path);
287+ }
288+
249 #[test]
289 #[test]
250 fn language_toml_map_merges() {
290 fn language_toml_map_merges() {
251 const USER: &str = r#"
291 const USER: &str = r#"
helix-lsp/src/lib.rs+1−1
@@ -931,7 +931,7 @@ pub fn find_lsp_workspace(
931 let mut file = if file.is_absolute() {
931 let mut file = if file.is_absolute() {
932 file.to_path_buf()
932 file.to_path_buf()
933 } else {
933 } else {
934− let current_dir = std::env::current_dir().expect("unable to determine current directory");
934+ let current_dir = helix_loader::current_working_dir();
935 current_dir.join(file)
935 current_dir.join(file)
936 };
936 };
937 file = path::get_normalized_path(&file);
937 file = path::get_normalized_path(&file);
helix-term/src/application.rs+1−1
@@ -163,7 +163,7 @@ impl Application {
163 } else if !args.files.is_empty() {
163 } else if !args.files.is_empty() {
164 let first = &args.files[0].0; // we know it's not empty
164 let first = &args.files[0].0; // we know it's not empty
165 if first.is_dir() {
165 if first.is_dir() {
166− std::env::set_current_dir(first).context("set current dir")?;
166+ helix_loader::set_current_working_dir(first.clone())?;
167 editor.new_file(Action::VerticalSplit);
167 editor.new_file(Action::VerticalSplit);
168 let picker = ui::file_picker(".".into(), &config.load().editor);
168 let picker = ui::file_picker(".".into(), &config.load().editor);
169 compositor.push(Box::new(overlaid(picker)));
169 compositor.push(Box::new(overlaid(picker)));
helix-term/src/commands.rs+19−4
@@ -2080,8 +2080,12 @@ fn global_search(cx: &mut Context) {
2080 .binary_detection(BinaryDetection::quit(b'\x00'))
2080 .binary_detection(BinaryDetection::quit(b'\x00'))
2081 .build();
2081 .build();
2082
2082
2083− let search_root = std::env::current_dir()
2083+ let search_root = helix_loader::current_working_dir();
2084− .expect("Global search error: Failed to get current dir");
2084+ if !search_root.exists() {
2085+ editor.set_error("Current working directory does not exist");
2086+ return;
2087+ }
2088+
2085 let dedup_symlinks = file_picker_config.deduplicate_links;
2089 let dedup_symlinks = file_picker_config.deduplicate_links;
2086 let absolute_root = search_root
2090 let absolute_root = search_root
2087 .canonicalize()
2091 .canonicalize()
@@ -2173,7 +2177,9 @@ fn global_search(cx: &mut Context) {
2173 let call: job::Callback = Callback::EditorCompositor(Box::new(
2177 let call: job::Callback = Callback::EditorCompositor(Box::new(
2174 move |editor: &mut Editor, compositor: &mut Compositor| {
2178 move |editor: &mut Editor, compositor: &mut Compositor| {
2175 if all_matches.is_empty() {
2179 if all_matches.is_empty() {
2180+ if !editor.is_err() {
2176− editor.set_status("No matches found");
2181+ editor.set_status("No matches found");
2182+ }
2177 return;
2183 return;
2178 }
2184 }
2179
2185
@@ -2518,6 +2524,10 @@ fn append_mode(cx: &mut Context) {
2518
2524
2519fn file_picker(cx: &mut Context) {
2525fn file_picker(cx: &mut Context) {
2520 let root = find_workspace().0;
2526 let root = find_workspace().0;
2527+ if !root.exists() {
2528+ cx.editor.set_error("Workspace directory does not exist");
2529+ return;
2530+ }
2521 let picker = ui::file_picker(root, &cx.editor.config());
2531 let picker = ui::file_picker(root, &cx.editor.config());
2522 cx.push_layer(Box::new(overlaid(picker)));
2532 cx.push_layer(Box::new(overlaid(picker)));
2523}
2533}
@@ -2539,7 +2549,12 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
2539 cx.push_layer(Box::new(overlaid(picker)));
2549 cx.push_layer(Box::new(overlaid(picker)));
2540}
2550}
2541fn file_picker_in_current_directory(cx: &mut Context) {
2551fn file_picker_in_current_directory(cx: &mut Context) {
2542− let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
2552+ let cwd = helix_loader::current_working_dir();
2553+ if !cwd.exists() {
2554+ cx.editor
2555+ .set_error("Current working directory does not exist");
2556+ return;
2557+ }
2543 let picker = ui::file_picker(cwd, &cx.editor.config());
2558 let picker = ui::file_picker(cwd, &cx.editor.config());
2544 cx.push_layer(Box::new(overlaid(picker)));
2559 cx.push_layer(Box::new(overlaid(picker)));
2545}
2560}
helix-term/src/commands/dap.rs+1−1
@@ -217,7 +217,7 @@ pub fn dap_start_impl(
217 }
217 }
218 }
218 }
219
219
220− args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
220+ args.insert("cwd", to_value(helix_loader::current_working_dir())?);
221
221
222 let args = to_value(args).unwrap();
222 let args = to_value(args).unwrap();
223
223
helix-term/src/commands/lsp.rs+1−1
@@ -1033,7 +1033,7 @@ fn goto_impl(
1033 locations: Vec<lsp::Location>,
1033 locations: Vec<lsp::Location>,
1034 offset_encoding: OffsetEncoding,
1034 offset_encoding: OffsetEncoding,
1035) {
1035) {
1036− let cwdir = std::env::current_dir().unwrap_or_default();
1036+ let cwdir = helix_loader::current_working_dir();
1037
1037
1038 match locations.as_slice() {
1038 match locations.as_slice() {
1039 [location] => {
1039 [location] => {
helix-term/src/commands/typed.rs+10−8
@@ -1093,14 +1093,11 @@ fn change_current_directory(
1093 .as_ref(),
1093 .as_ref(),
1094 );
1094 );
1095
1095
1096− if let Err(e) = std::env::set_current_dir(dir) {
1096+ helix_loader::set_current_working_dir(dir)?;
1097− bail!("Couldn't change the current working directory: {}", e);
1098− }
1099
1097
1100− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1101 cx.editor.set_status(format!(
1098 cx.editor.set_status(format!(
1102 "Current working directory is now {}",
1099 "Current working directory is now {}",
1103− cwd.display()
1100+ helix_loader::current_working_dir().display()
1104 ));
1101 ));
1105 Ok(())
1102 Ok(())
1106}
1103}
@@ -1114,9 +1111,14 @@ fn show_current_directory(
1114 return Ok(());
1111 return Ok(());
1115 }
1112 }
1116
1113
1117− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1114+ let cwd = helix_loader::current_working_dir();
1118− cx.editor
1119− .set_status(format!("Current working directory is {}", cwd.display()));
1115+ let message = format!("Current working directory is {}", cwd.display());
1116+
1117+ if cwd.exists() {
1118+ cx.editor.set_status(message);
1119+ } else {
1120+ cx.editor.set_error(format!("{} (deleted)", message));
1121+ }
1120 Ok(())
1122 Ok(())
1121}
1123}
1122
1124
helix-term/src/ui/mod.rs+1−1
@@ -472,7 +472,7 @@ pub mod completers {
472 match path.parent() {
472 match path.parent() {
473 Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(),
473 Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(),
474 // Path::new("h")'s parent is Some("")...
474 // Path::new("h")'s parent is Some("")...
475− _ => std::env::current_dir().expect("couldn't determine current directory"),
475+ _ => helix_loader::current_working_dir(),
476 }
476 }
477 };
477 };
478
478