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 <anthony@zed.dev>
This commit is contained in:
Xipeng Jin
2025-12-03 11:59:56 -05:00
committed by GitHub
co-authored by Anthony Eid
parent b168679c18
commit 85ccd7c98b
3 changed files with 40 additions and 12 deletions
+18 -7
View File
@@ -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<dyn language::File>>) -> 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<Vec<Range<MultiBufferOffsetUtf16>>> {
+9 -4
View File
@@ -1891,15 +1891,20 @@ fn path_for_buffer<'a>(
cx: &'a App,
) -> Option<Cow<'a, str>> {
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<dyn language::File>,
mut height: usize,
include_filename: bool,
cx: &'a App,
) -> Option<Cow<'a, str>> {
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<dyn language::File> = Arc::new(TestFile {
path: RelPath::empty().into(),
root_name: String::new(),
local_root: None,
};
});
assert_eq!(path_for_file(&file, 0, false, cx), None);
}
+13 -1
View File
@@ -68,6 +68,7 @@ struct GitBlob {
path: RepoPath,
worktree_id: WorktreeId,
is_deleted: bool,
display_name: Arc<str>,
}
const FILE_NAMESPACE_SORT_PREFIX: u64 = 1;
@@ -157,6 +158,7 @@ impl CommitView {
});
editor
});
let commit_sha = Arc::<str>::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<str> =
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<dyn language::File>;
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 {