From 85ccd7c98b0b35381ef77c7383995e892db0689f Mon Sep 17 00:00:00 2001 From: Xipeng Jin <56369076+xipeng-jin@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:59:56 -0500 Subject: [PATCH] Fix not able to navigate to files in git commit multibuffer (#42558) Closes #40851 Release Notes: - Fixed: Commit diff multibuffers now open real project files whenever possible, restoring navigation and annotations inside those excerpts. --------- Co-authored-by: Anthony Eid --- crates/editor/src/editor.rs | 25 ++++++++++++++++++------- crates/editor/src/items.rs | 13 +++++++++---- crates/git_ui/src/commit_view.rs | 14 +++++++++++++- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6f936a211d3b5eb308b26e4351350666e616bf6c..eba10e4ea2a3663191fc3739b3b2f7f73101b7f5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -21936,10 +21936,17 @@ impl Editor { }; for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer { - let editor = buffer - .read(cx) - .file() - .is_none() + let buffer_read = buffer.read(cx); + let (has_file, is_project_file) = if let Some(file) = buffer_read.file() { + (true, project::File::from_dyn(Some(file)).is_some()) + } else { + (false, false) + }; + + // If project file is none workspace.open_project_item will fail to open the excerpt + // in a pre existing workspace item if one exists, because Buffer entity_id will be None + // so we check if there's a tab match in that case first + let editor = (!has_file || !is_project_file) .then(|| { // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id, // so `workspace.open_project_item` will never find them, always opening a new editor. @@ -21973,6 +21980,9 @@ impl Editor { }); editor.update(cx, |editor, cx| { + if has_file && !is_project_file { + editor.set_read_only(true); + } let autoscroll = match scroll_offset { Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize), None => Autoscroll::newest(), @@ -21996,10 +22006,11 @@ impl Editor { }); } - // For now, don't allow opening excerpts in buffers that aren't backed by - // regular project files. + // Allow opening excerpts for buffers that either belong to the current project + // or represent synthetic/non-local files (e.g., git blobs). File-less buffers + // are also supported so tests and other in-memory views keep working. fn can_open_excerpts_in_file(file: Option<&Arc>) -> bool { - file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some()) + file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local()) } fn marked_text_ranges(&self, cx: &App) -> Option>> { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8111c837e2ee5c35fdfb120999c2be49b09c468c..4e1305866ee9e4219295c02bdc519b4bc857cddf 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1891,15 +1891,20 @@ fn path_for_buffer<'a>( cx: &'a App, ) -> Option> { let file = buffer.read(cx).as_singleton()?.read(cx).file()?; - path_for_file(file.as_ref(), height, include_filename, cx) + path_for_file(file, height, include_filename, cx) } fn path_for_file<'a>( - file: &'a dyn language::File, + file: &'a Arc, mut height: usize, include_filename: bool, cx: &'a App, ) -> Option> { + if project::File::from_dyn(Some(file)).is_none() { + return None; + } + + let file = file.as_ref(); // Ensure we always render at least the filename. height += 1; @@ -1946,11 +1951,11 @@ mod tests { #[gpui::test] fn test_path_for_file(cx: &mut App) { - let file = TestFile { + let file: Arc = Arc::new(TestFile { path: RelPath::empty().into(), root_name: String::new(), local_root: None, - }; + }); assert_eq!(path_for_file(&file, 0, false, cx), None); } diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 4f6633a18c031b8f231f43f8b0efc13e7fd710a7..31ac8139a63be218f652204ebe29d43e526c5a02 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -68,6 +68,7 @@ struct GitBlob { path: RepoPath, worktree_id: WorktreeId, is_deleted: bool, + display_name: Arc, } const FILE_NAMESPACE_SORT_PREFIX: u64 = 1; @@ -157,6 +158,7 @@ impl CommitView { }); editor }); + let commit_sha = Arc::::from(commit.sha.as_ref()); let first_worktree_id = project .read(cx) @@ -180,10 +182,20 @@ impl CommitView { .or(first_worktree_id) })? .context("project has no worktrees")?; + let short_sha = commit_sha.get(0..7).unwrap_or(&commit_sha); + let file_name = file + .path + .file_name() + .map(|name| name.to_string()) + .unwrap_or_else(|| file.path.display(PathStyle::Posix).to_string()); + let display_name: Arc = + Arc::from(format!("{short_sha} - {file_name}").into_boxed_str()); + let file = Arc::new(GitBlob { path: file.path.clone(), is_deleted, worktree_id, + display_name, }) as Arc; let buffer = build_buffer(new_text, file, &language_registry, cx).await?; @@ -647,7 +659,7 @@ impl language::File for GitBlob { } fn file_name<'a>(&'a self, _: &'a App) -> &'a str { - self.path.file_name().unwrap() + self.display_name.as_ref() } fn worktree_id(&self, _: &App) -> WorktreeId {