From 795eb3409826a6e101fe77ffd4b19086f8f7aa79 Mon Sep 17 00:00:00 2001 From: Austin Cummings Date: Sat, 31 Jan 2026 00:57:31 -0700 Subject: [PATCH] Fix open path prompt not showing hidden files (#46965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #39036 The open path prompt will now show hidden files when "." is entered. Also fixes an issue with "open this directory" showing twice when used by the "toolchain: add toolchain" prompt. With a tree of ``` zed-industries ├── .hidden ├── .hidden-file ├── zed ├── zed-working ├── zeta └── zeta-dataset ``` **Before:** image **After (current directory view without inputting "."):** image **After (when inputting "." to see hidden entries):** image Release Notes: - Made Zed's built in file picker to show all hidden files by default --- .../open_path_prompt/src/open_path_prompt.rs | 90 +++++++++++-------- .../src/open_path_prompt_tests.rs | 35 +++++++- 2 files changed, 87 insertions(+), 38 deletions(-) diff --git a/crates/open_path_prompt/src/open_path_prompt.rs b/crates/open_path_prompt/src/open_path_prompt.rs index fbdcb17808234ae3ef1467810c0a521c8916704e..fa609a63be1101ce86df61b6a13bcfe6a65c586d 100644 --- a/crates/open_path_prompt/src/open_path_prompt.rs +++ b/crates/open_path_prompt/src/open_path_prompt.rs @@ -225,7 +225,8 @@ impl OpenPathPrompt { cx: &mut Context, ) { workspace.toggle_modal(window, cx, |window, cx| { - let delegate = OpenPathDelegate::new(tx, lister.clone(), creating_path, cx); + let delegate = + OpenPathDelegate::new(tx, lister.clone(), creating_path, cx).show_hidden(); let picker = Picker::uniform_list(delegate, window, cx).width(rems(34.)); let mut query = lister.default_query(cx); if let Some(suggested_name) = suggested_name { @@ -402,14 +403,15 @@ impl PickerDelegate for OpenPathDelegate { return; }; + if !hidden_entries { + new_entries.retain(|entry| !entry.path.string.starts_with('.')); + } + let max_id = new_entries .iter() .map(|entry| entry.path.id) .max() .unwrap_or(0); - if !suffix.starts_with('.') && !hidden_entries { - new_entries.retain(|entry| !entry.path.string.starts_with('.')); - } if suffix.is_empty() { let should_prepend_with_current_dir = this @@ -489,6 +491,8 @@ impl PickerDelegate for OpenPathDelegate { if is_create_state && !entry.is_dir && Some(&suffix) == Some(&entry.path.string) { None + } else if !suffix.is_empty() && entry.path.string == current_dir { + None } else { Some(&entry.path) } @@ -892,33 +896,6 @@ fn path_candidates( .collect() } -#[cfg(target_os = "windows")] -fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String) { - let last_item = Path::new(&query) - .file_name() - .unwrap_or_default() - .to_string_lossy(); - let (mut dir, suffix) = if let Some(dir) = query.strip_suffix(last_item.as_ref()) { - (dir.to_string(), last_item.into_owned()) - } else { - (query.to_string(), String::new()) - }; - match path_style { - PathStyle::Posix => { - if dir.is_empty() { - dir = "/".to_string(); - } - } - PathStyle::Windows => { - if dir.len() < 3 { - dir = "C:\\".to_string(); - } - } - } - (dir, suffix) -} - -#[cfg(not(target_os = "windows"))] fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String) { match path_style { PathStyle::Posix => { @@ -933,17 +910,18 @@ fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String) (dir, suffix) } PathStyle::Windows => { - let (mut dir, suffix) = if let Some(index) = query.rfind('\\') { - (query[..index].to_string(), query[index + 1..].to_string()) + let last_sep = query.rfind('\\').into_iter().chain(query.rfind('/')).max(); + let (mut dir, suffix) = if let Some(index) = last_sep { + ( + query[..index + 1].to_string(), + query[index + 1..].to_string(), + ) } else { (query, String::new()) }; if dir.len() < 3 { dir = "C:\\".to_string(); } - if !dir.ends_with('\\') { - dir.push('\\'); - } (dir, suffix) } } @@ -987,6 +965,34 @@ mod tests { get_dir_and_suffix("C:\\Users\\Junkui\\Documents\\".into(), PathStyle::Windows); assert_eq!(dir, "C:\\Users\\Junkui\\Documents\\"); assert_eq!(suffix, ""); + + let (dir, suffix) = get_dir_and_suffix("C:\\root\\.".into(), PathStyle::Windows); + assert_eq!(dir, "C:\\root\\"); + assert_eq!(suffix, "."); + + let (dir, suffix) = get_dir_and_suffix("C:\\root\\..".into(), PathStyle::Windows); + assert_eq!(dir, "C:\\root\\"); + assert_eq!(suffix, ".."); + + let (dir, suffix) = get_dir_and_suffix("C:\\root\\.hidden".into(), PathStyle::Windows); + assert_eq!(dir, "C:\\root\\"); + assert_eq!(suffix, ".hidden"); + + let (dir, suffix) = get_dir_and_suffix("C:/root/".into(), PathStyle::Windows); + assert_eq!(dir, "C:/root/"); + assert_eq!(suffix, ""); + + let (dir, suffix) = get_dir_and_suffix("C:/root/Use".into(), PathStyle::Windows); + assert_eq!(dir, "C:/root/"); + assert_eq!(suffix, "Use"); + + let (dir, suffix) = get_dir_and_suffix("C:\\root/Use".into(), PathStyle::Windows); + assert_eq!(dir, "C:\\root/"); + assert_eq!(suffix, "Use"); + + let (dir, suffix) = get_dir_and_suffix("C:/root\\.hidden".into(), PathStyle::Windows); + assert_eq!(dir, "C:/root\\"); + assert_eq!(suffix, ".hidden"); } #[test] @@ -1014,5 +1020,17 @@ mod tests { let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Documents/".into(), PathStyle::Posix); assert_eq!(dir, "/Users/Junkui/Documents/"); assert_eq!(suffix, ""); + + let (dir, suffix) = get_dir_and_suffix("/root/.".into(), PathStyle::Posix); + assert_eq!(dir, "/root/"); + assert_eq!(suffix, "."); + + let (dir, suffix) = get_dir_and_suffix("/root/..".into(), PathStyle::Posix); + assert_eq!(dir, "/root/"); + assert_eq!(suffix, ".."); + + let (dir, suffix) = get_dir_and_suffix("/root/.hidden".into(), PathStyle::Posix); + assert_eq!(dir, "/root/"); + assert_eq!(suffix, ".hidden"); } } diff --git a/crates/open_path_prompt/src/open_path_prompt_tests.rs b/crates/open_path_prompt/src/open_path_prompt_tests.rs index bfb2628e9bf21a30e8a8a79d8f20aa32b5b4f0f3..d8e96b24b5bcbd76c8d729240d1f1d822f18df79 100644 --- a/crates/open_path_prompt/src/open_path_prompt_tests.rs +++ b/crates/open_path_prompt/src/open_path_prompt_tests.rs @@ -19,6 +19,8 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) { .insert_tree( path!("/root"), json!({ + ".a1": ".A1", + ".b1": ".B1", "a1": "A1", "a2": "A2", "a3": "A3", @@ -51,7 +53,7 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) { #[cfg(windows)] let expected_separator = ".\\"; - // If the query ends with a slash, the picker should show the contents of the directory. + // If the query ends with a slash, the picker should show the contents of the directory and not show any of the hidden entries. let query = path!("/root/"); insert_query(query, &picker, cx).await; assert_eq!( @@ -94,6 +96,33 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) { let query = path!("/root/dir2/di"); insert_query(query, &picker, cx).await; assert_eq!(collect_match_candidates(&picker, cx), vec!["dir3", "dir4"]); + + // Don't show candidates for the query ".". + let query = path!("/root/."); + insert_query(query, &picker, cx).await; + assert_eq!(collect_match_candidates(&picker, cx), Vec::::new()); + + // Don't show any candidates for the query ".a". + let query = path!("/root/.a"); + insert_query(query, &picker, cx).await; + assert_eq!(collect_match_candidates(&picker, cx), Vec::::new()); + + // Show candidates for the query "./". + // Should show current directory and contents. + let query = path!("/root/./"); + insert_query(query, &picker, cx).await; + assert_eq!( + collect_match_candidates(&picker, cx), + vec![expected_separator, "a1", "a2", "a3", "dir1", "dir2"] + ); + + // Show candidates for the query "../". Show parent contents. + let query = path!("/root/dir1/../"); + insert_query(query, &picker, cx).await; + assert_eq!( + collect_match_candidates(&picker, cx), + vec![expected_separator, "a1", "a2", "a3", "dir1", "dir2"] + ); } #[gpui::test] @@ -369,11 +398,13 @@ async fn test_open_path_prompt_with_show_hidden(cx: &mut TestAppContext) { let expected_separator = ".\\"; insert_query(path!("/root/"), &picker, cx).await; - assert_eq!( collect_match_candidates(&picker, cx), vec![expected_separator, ".hidden", "directory_1", "directory_2"] ); + + insert_query(path!("/root/."), &picker, cx).await; + assert_eq!(collect_match_candidates(&picker, cx), vec![".hidden"]); } fn init_test(cx: &mut TestAppContext) -> Arc {