diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 473d590356..9c23e7ff8c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2184,9 +2184,14 @@ impl ProjectPanel { }); let file_name = entry.path.file_name().unwrap_or_default().to_string(); let selection = selection.unwrap_or_else(|| { - let file_stem = entry.path.file_stem().map(|s| s.to_string()); - let selection_end = - file_stem.map_or(file_name.len(), |file_stem| file_stem.len()); + // Folders have no extension, so select the whole name. Only + // files keep their extension unselected for quick renames. + let selection_end = if entry.is_dir() { + file_name.len() + } else { + let file_stem = entry.path.file_stem(); + file_stem.map_or(file_name.len(), |file_stem| file_stem.len()) + }; 0..selection_end }); self.filename_editor.update(cx, |editor, cx| { diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 8a745af785..a228749dc1 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -1165,6 +1165,71 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_rename_folder_with_dot_selects_whole_name(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + "/root1", + json!({ + "my.folder": {}, + "archive.tar.gz": "", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await; + let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let workspace = window + .read_with(cx, |mw, _| mw.workspace().clone()) + .unwrap(); + let cx = &mut VisualTestContext::from_window(window.into(), cx); + let panel = workspace.update_in(cx, |workspace, window, cx| { + let panel = ProjectPanel::new(workspace, window, cx); + workspace.add_panel(panel.clone(), window, cx); + panel + }); + cx.run_until_parked(); + + // Renaming a folder whose name contains a dot should pre-select the whole + // name. The dot belongs to the directory name; it is not a file extension. + select_path(&panel, "root1/my.folder", cx); + panel.update_in(cx, |panel, window, cx| panel.rename(&Rename, window, cx)); + panel.update_in(cx, |panel, window, cx| { + panel.filename_editor.update(cx, |editor, cx| { + let selections = editor + .selections + .all::(&editor.display_snapshot(cx)); + assert_eq!(selections.len(), 1); + assert_eq!(selections[0].start, MultiBufferOffset(0)); + assert_eq!( + selections[0].end, + MultiBufferOffset("my.folder".len()), + "Renaming a folder should select the whole name, including dots" + ); + }); + panel.cancel(&Cancel, window, cx); + }); + cx.run_until_parked(); + + // Files keep the existing behavior: the last extension stays unselected. + select_path(&panel, "root1/archive.tar.gz", cx); + panel.update_in(cx, |panel, window, cx| panel.rename(&Rename, window, cx)); + panel.update_in(cx, |panel, _, cx| { + panel.filename_editor.update(cx, |editor, cx| { + let selections = editor + .selections + .all::(&editor.display_snapshot(cx)); + assert_eq!( + selections[0].end, + MultiBufferOffset("archive.tar".len()), + "Renaming a file should keep the last extension unselected" + ); + }); + }); +} + #[gpui::test(iterations = 10)] async fn test_adding_directories_via_file(cx: &mut gpui::TestAppContext) { init_test(cx);