project_panel: Do not allow creating empty file/dir or file/dir with only whitespaces (#28240)

Smit Barmase created

- Do not allow creating empty file or empty directory.
- Do not allow creating file or directory with just whitespace.
- Show error only in case whitespace.

<img width="352" alt="image"
src="https://github.com/user-attachments/assets/f6040332-59a6-4d09-bf07-2b4b1b8b9e03"
/>

Release Notes:

- N/A

Change summary

crates/project_panel/src/project_panel.rs       | 38 +++++++++------
crates/project_panel/src/project_panel_tests.rs | 44 +++++++++++++++++++
2 files changed, 66 insertions(+), 16 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -1150,9 +1150,7 @@ impl ProjectPanel {
             Some(state) => state,
             None => return,
         };
-
         let filename = self.filename_editor.read(cx).text(cx);
-
         if !filename.is_empty() {
             if let Some(worktree) = self
                 .project
@@ -1160,6 +1158,7 @@ impl ProjectPanel {
                 .worktree_for_id(edit_state.worktree_id, cx)
             {
                 if let Some(entry) = worktree.read(cx).entry_for_id(edit_state.entry_id) {
+                    let mut already_exists = false;
                     if edit_state.is_new_entry() {
                         let new_path = entry.path.join(filename.trim_start_matches('/'));
                         if worktree
@@ -1167,12 +1166,7 @@ impl ProjectPanel {
                             .entry_for_path(new_path.as_path())
                             .is_some()
                         {
-                            edit_state.validation_state = ValidationState::Error(format!(
-                                "File or directory '{}' already exists at location. Please choose a different name.",
-                                filename
-                            ));
-                            cx.notify();
-                            return;
+                            already_exists = true;
                         }
                     } else {
                         let new_path = if let Some(parent) = entry.path.clone().parent() {
@@ -1183,18 +1177,28 @@ impl ProjectPanel {
                         if let Some(existing) = worktree.read(cx).entry_for_path(new_path.as_path())
                         {
                             if existing.id != entry.id {
-                                edit_state.validation_state = ValidationState::Error(
-                                    "File or directory already exists".to_string(),
-                                );
-                                cx.notify();
-                                return;
+                                already_exists = true;
                             }
                         }
                     };
+                    if already_exists {
+                        edit_state.validation_state = ValidationState::Error(format!(
+                            "File or directory '{}' already exists at location. Please choose a different name.",
+                            filename
+                        ));
+                        cx.notify();
+                        return;
+                    }
                 }
             }
-
-            if filename.trim() != filename {
+            let trimmed_filename = filename.trim();
+            if trimmed_filename.is_empty() {
+                edit_state.validation_state =
+                    ValidationState::Error("File or directory name cannot be empty.".to_string());
+                cx.notify();
+                return;
+            }
+            if trimmed_filename != filename {
                 edit_state.validation_state = ValidationState::Warning(
                     "File or directory name contains leading or trailing whitespace.".to_string(),
                 );
@@ -1202,7 +1206,6 @@ impl ProjectPanel {
                 return;
             }
         }
-
         edit_state.validation_state = ValidationState::None;
         cx.notify();
     }
@@ -1216,6 +1219,9 @@ impl ProjectPanel {
         let worktree_id = edit_state.worktree_id;
         let is_new_entry = edit_state.is_new_entry();
         let filename = self.filename_editor.read(cx).text(cx);
+        if filename.trim().is_empty() {
+            return None;
+        }
         #[cfg(not(target_os = "windows"))]
         let filename_indicates_dir = filename.ends_with("/");
         // On Windows, path separator could be either `/` or `\`.

crates/project_panel/src/project_panel_tests.rs 🔗

@@ -766,6 +766,50 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) {
             "      .dockerignore",
         ]
     );
+
+    // Test empty filename and filename with only whitespace
+    panel.update_in(cx, |panel, window, cx| panel.new_file(&NewFile, window, cx));
+    assert_eq!(
+        visible_entries_as_strings(&panel, 0..10, cx),
+        &[
+            "v root1",
+            "    > .git",
+            "    > a",
+            "    v b",
+            "        > 3",
+            "        > 4",
+            "        > new-dir",
+            "          [EDITOR: '']  <== selected",
+            "          a-different-filename.tar.gz",
+            "    > C",
+        ]
+    );
+    panel.update_in(cx, |panel, window, cx| {
+        panel.filename_editor.update(cx, |editor, cx| {
+            editor.set_text("", window, cx);
+        });
+        assert!(panel.confirm_edit(window, cx).is_none());
+        panel.filename_editor.update(cx, |editor, cx| {
+            editor.set_text("   ", window, cx);
+        });
+        assert!(panel.confirm_edit(window, cx).is_none());
+        panel.cancel(&menu::Cancel, window, cx)
+    });
+    assert_eq!(
+        visible_entries_as_strings(&panel, 0..10, cx),
+        &[
+            "v root1",
+            "    > .git",
+            "    > a",
+            "    v b",
+            "        > 3",
+            "        > 4",
+            "        > new-dir",
+            "          a-different-filename.tar.gz  <== selected",
+            "    > C",
+            "      .dockerignore",
+        ]
+    );
 }
 
 #[gpui::test(iterations = 10)]