Skip to content

Commit 795eb34

Browse files
Fix open path prompt not showing hidden files (zed-industries#46965)
Closes zed-industries#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:** <img width="656" height="174" alt="image" src="https://github.com/user-attachments/assets/abf30ce3-b1c2-4a14-a45d-c17b6c3aef6f" /> **After (current directory view without inputting "."):** <img width="648" height="261" alt="image" src="https://github.com/user-attachments/assets/00c65546-32c1-4c85-a05c-53152ab2f942" /> **After (when inputting "." to see hidden entries):** <img width="618" height="156" alt="image" src="https://github.com/user-attachments/assets/8453ae89-b1a7-44d4-9f7d-ed89e55a7020" /> Release Notes: - Made Zed's built in file picker to show all hidden files by default
1 parent 03663b9 commit 795eb34

File tree

2 files changed

+87
-38
lines changed

2 files changed

+87
-38
lines changed

crates/open_path_prompt/src/open_path_prompt.rs

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ impl OpenPathPrompt {
225225
cx: &mut Context<Workspace>,
226226
) {
227227
workspace.toggle_modal(window, cx, |window, cx| {
228-
let delegate = OpenPathDelegate::new(tx, lister.clone(), creating_path, cx);
228+
let delegate =
229+
OpenPathDelegate::new(tx, lister.clone(), creating_path, cx).show_hidden();
229230
let picker = Picker::uniform_list(delegate, window, cx).width(rems(34.));
230231
let mut query = lister.default_query(cx);
231232
if let Some(suggested_name) = suggested_name {
@@ -402,14 +403,15 @@ impl PickerDelegate for OpenPathDelegate {
402403
return;
403404
};
404405

406+
if !hidden_entries {
407+
new_entries.retain(|entry| !entry.path.string.starts_with('.'));
408+
}
409+
405410
let max_id = new_entries
406411
.iter()
407412
.map(|entry| entry.path.id)
408413
.max()
409414
.unwrap_or(0);
410-
if !suffix.starts_with('.') && !hidden_entries {
411-
new_entries.retain(|entry| !entry.path.string.starts_with('.'));
412-
}
413415

414416
if suffix.is_empty() {
415417
let should_prepend_with_current_dir = this
@@ -489,6 +491,8 @@ impl PickerDelegate for OpenPathDelegate {
489491
if is_create_state && !entry.is_dir && Some(&suffix) == Some(&entry.path.string)
490492
{
491493
None
494+
} else if !suffix.is_empty() && entry.path.string == current_dir {
495+
None
492496
} else {
493497
Some(&entry.path)
494498
}
@@ -892,33 +896,6 @@ fn path_candidates(
892896
.collect()
893897
}
894898

895-
#[cfg(target_os = "windows")]
896-
fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String) {
897-
let last_item = Path::new(&query)
898-
.file_name()
899-
.unwrap_or_default()
900-
.to_string_lossy();
901-
let (mut dir, suffix) = if let Some(dir) = query.strip_suffix(last_item.as_ref()) {
902-
(dir.to_string(), last_item.into_owned())
903-
} else {
904-
(query.to_string(), String::new())
905-
};
906-
match path_style {
907-
PathStyle::Posix => {
908-
if dir.is_empty() {
909-
dir = "/".to_string();
910-
}
911-
}
912-
PathStyle::Windows => {
913-
if dir.len() < 3 {
914-
dir = "C:\\".to_string();
915-
}
916-
}
917-
}
918-
(dir, suffix)
919-
}
920-
921-
#[cfg(not(target_os = "windows"))]
922899
fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String) {
923900
match path_style {
924901
PathStyle::Posix => {
@@ -933,17 +910,18 @@ fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String)
933910
(dir, suffix)
934911
}
935912
PathStyle::Windows => {
936-
let (mut dir, suffix) = if let Some(index) = query.rfind('\\') {
937-
(query[..index].to_string(), query[index + 1..].to_string())
913+
let last_sep = query.rfind('\\').into_iter().chain(query.rfind('/')).max();
914+
let (mut dir, suffix) = if let Some(index) = last_sep {
915+
(
916+
query[..index + 1].to_string(),
917+
query[index + 1..].to_string(),
918+
)
938919
} else {
939920
(query, String::new())
940921
};
941922
if dir.len() < 3 {
942923
dir = "C:\\".to_string();
943924
}
944-
if !dir.ends_with('\\') {
945-
dir.push('\\');
946-
}
947925
(dir, suffix)
948926
}
949927
}
@@ -987,6 +965,34 @@ mod tests {
987965
get_dir_and_suffix("C:\\Users\\Junkui\\Documents\\".into(), PathStyle::Windows);
988966
assert_eq!(dir, "C:\\Users\\Junkui\\Documents\\");
989967
assert_eq!(suffix, "");
968+
969+
let (dir, suffix) = get_dir_and_suffix("C:\\root\\.".into(), PathStyle::Windows);
970+
assert_eq!(dir, "C:\\root\\");
971+
assert_eq!(suffix, ".");
972+
973+
let (dir, suffix) = get_dir_and_suffix("C:\\root\\..".into(), PathStyle::Windows);
974+
assert_eq!(dir, "C:\\root\\");
975+
assert_eq!(suffix, "..");
976+
977+
let (dir, suffix) = get_dir_and_suffix("C:\\root\\.hidden".into(), PathStyle::Windows);
978+
assert_eq!(dir, "C:\\root\\");
979+
assert_eq!(suffix, ".hidden");
980+
981+
let (dir, suffix) = get_dir_and_suffix("C:/root/".into(), PathStyle::Windows);
982+
assert_eq!(dir, "C:/root/");
983+
assert_eq!(suffix, "");
984+
985+
let (dir, suffix) = get_dir_and_suffix("C:/root/Use".into(), PathStyle::Windows);
986+
assert_eq!(dir, "C:/root/");
987+
assert_eq!(suffix, "Use");
988+
989+
let (dir, suffix) = get_dir_and_suffix("C:\\root/Use".into(), PathStyle::Windows);
990+
assert_eq!(dir, "C:\\root/");
991+
assert_eq!(suffix, "Use");
992+
993+
let (dir, suffix) = get_dir_and_suffix("C:/root\\.hidden".into(), PathStyle::Windows);
994+
assert_eq!(dir, "C:/root\\");
995+
assert_eq!(suffix, ".hidden");
990996
}
991997

992998
#[test]
@@ -1014,5 +1020,17 @@ mod tests {
10141020
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Documents/".into(), PathStyle::Posix);
10151021
assert_eq!(dir, "/Users/Junkui/Documents/");
10161022
assert_eq!(suffix, "");
1023+
1024+
let (dir, suffix) = get_dir_and_suffix("/root/.".into(), PathStyle::Posix);
1025+
assert_eq!(dir, "/root/");
1026+
assert_eq!(suffix, ".");
1027+
1028+
let (dir, suffix) = get_dir_and_suffix("/root/..".into(), PathStyle::Posix);
1029+
assert_eq!(dir, "/root/");
1030+
assert_eq!(suffix, "..");
1031+
1032+
let (dir, suffix) = get_dir_and_suffix("/root/.hidden".into(), PathStyle::Posix);
1033+
assert_eq!(dir, "/root/");
1034+
assert_eq!(suffix, ".hidden");
10171035
}
10181036
}

crates/open_path_prompt/src/open_path_prompt_tests.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) {
1919
.insert_tree(
2020
path!("/root"),
2121
json!({
22+
".a1": ".A1",
23+
".b1": ".B1",
2224
"a1": "A1",
2325
"a2": "A2",
2426
"a3": "A3",
@@ -51,7 +53,7 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) {
5153
#[cfg(windows)]
5254
let expected_separator = ".\\";
5355

54-
// If the query ends with a slash, the picker should show the contents of the directory.
56+
// If the query ends with a slash, the picker should show the contents of the directory and not show any of the hidden entries.
5557
let query = path!("/root/");
5658
insert_query(query, &picker, cx).await;
5759
assert_eq!(
@@ -94,6 +96,33 @@ async fn test_open_path_prompt(cx: &mut TestAppContext) {
9496
let query = path!("/root/dir2/di");
9597
insert_query(query, &picker, cx).await;
9698
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir3", "dir4"]);
99+
100+
// Don't show candidates for the query ".".
101+
let query = path!("/root/.");
102+
insert_query(query, &picker, cx).await;
103+
assert_eq!(collect_match_candidates(&picker, cx), Vec::<String>::new());
104+
105+
// Don't show any candidates for the query ".a".
106+
let query = path!("/root/.a");
107+
insert_query(query, &picker, cx).await;
108+
assert_eq!(collect_match_candidates(&picker, cx), Vec::<String>::new());
109+
110+
// Show candidates for the query "./".
111+
// Should show current directory and contents.
112+
let query = path!("/root/./");
113+
insert_query(query, &picker, cx).await;
114+
assert_eq!(
115+
collect_match_candidates(&picker, cx),
116+
vec![expected_separator, "a1", "a2", "a3", "dir1", "dir2"]
117+
);
118+
119+
// Show candidates for the query "../". Show parent contents.
120+
let query = path!("/root/dir1/../");
121+
insert_query(query, &picker, cx).await;
122+
assert_eq!(
123+
collect_match_candidates(&picker, cx),
124+
vec![expected_separator, "a1", "a2", "a3", "dir1", "dir2"]
125+
);
97126
}
98127

99128
#[gpui::test]
@@ -369,11 +398,13 @@ async fn test_open_path_prompt_with_show_hidden(cx: &mut TestAppContext) {
369398
let expected_separator = ".\\";
370399

371400
insert_query(path!("/root/"), &picker, cx).await;
372-
373401
assert_eq!(
374402
collect_match_candidates(&picker, cx),
375403
vec![expected_separator, ".hidden", "directory_1", "directory_2"]
376404
);
405+
406+
insert_query(path!("/root/."), &picker, cx).await;
407+
assert_eq!(collect_match_candidates(&picker, cx), vec![".hidden"]);
377408
}
378409

379410
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {

0 commit comments

Comments
 (0)